Esempio n. 1
0
class PerforceRepository(object):
    """A Perforce repository implementation.

    Built on top of the PyPerforce API.
    http://pyperforce.sourceforge.net/
    """

    def __init__(self, connection, log, jobPrefixLength):

        self.authz = None

        # length of the job prefix used with PerforceJobScript
        self._job_prefix_length = jobPrefixLength

        # Log object for logging output
        self._log = log

        # The connection to the Perforce server
        self._connection = connection

        # The Repository object that we query for Perforce info
        from p4trac.repos import Repository
        self._repos = Repository(connection)

    def get_name(self):
        return 'p4://%s:%s@%s' % (self._connection.user, self._connection.password, self._connection.port)
    name = property(get_name)

    def close(self):
        self._connection.disconnect()

    def get_tags(self, rev):

        results = self._connection.run('labels')

        if results.errors:
            from p4trac.repos import PerforceError
            raise PerforceError(results.errors)

        for rec in results.records:
            name = self._repos.toUnicode(rec['label'])
            yield (name, u'@%s' % name)


    def get_branches(self, rev):
        # TODO: Generate a list of branches
        return []

    def get_changeset(self, rev):

        self._log.debug('get_changeset(%r)' % rev)

        if isinstance(rev, int):
            change = rev
        else:
            from p4trac.util import toUnicode
            rev = toUnicode(rev)
                
            if rev.startswith(u'@'):
                rev = rev[1:]
                
            try:
                change = int(rev)
            except ValueError:
                raise TracError(u"Invalid changeset number '%s'" % rev)
            
        return PerforceChangeset(change, self._repos, self._log, self._job_prefix_length)

    def get_changesets(self, start, stop):

        self._log.debug('PerforceRepository.get_changesets(%r,%r)' % (start,
                                                                      stop))

        import datetime
        start = datetime.datetime.fromtimestamp(start)
        stop = datetime.datetime.fromtimestamp(stop)

        startDate = start.strftime('%Y/%m/%d:%H:%M:%S')
        stopDate = stop.strftime('%Y/%m/%d:%H:%M:%S')

        from p4trac.repos import _P4ChangesOutputConsumer
        output = _P4ChangesOutputConsumer(self._repos)
        self._connection.run('changes', '-l', '-s', 'submitted',
                             '@>=%s,@<=%s' % (startDate, stopDate),
                             output=output)

        if output.errors:
            from p4trac.repos import PerforceError
            raise PerforceError(output.errors)

        for change in output.changes:
            yield self.get_changeset(change)

    def has_node(self, path, rev=None):
        from p4trac.repos import NodePath
        path = NodePath.normalisePath(path)
        return self._repos.getNode(NodePath(path, rev)).exists

    def get_node(self, path, rev=None):
        self._log.debug('get_node(%s, %s) called' % (path, rev))

        from p4trac.repos import NodePath
        nodePath = NodePath(NodePath.normalisePath(path), rev)
        
        return PerforceNode(nodePath, self._repos, self._log)

    def get_oldest_rev(self):
        return self.next_rev(0)
    oldest_rev = property(fget=get_oldest_rev)

    def get_youngest_rev(self):
        return self._repos.getLatestChange()
    youngest_rev = property(fget=get_youngest_rev)

    def previous_rev(self, rev):

        self._log.debug('previous_rev(%r)' % rev)

        if not isinstance(rev, int):
            rev = self.short_rev(rev)
            if not isinstance(rev, int):
                raise NoSuchChangeset(rev)

        from p4trac.repos import _P4ChangesOutputConsumer
        output = _P4ChangesOutputConsumer(self._repos)
        self._connection.run('changes', '-l', '-s', 'submitted',
                             '-m', '1',
                             '@<%i' % rev,
                             output=output)

        if output.errors:
            from p4trac.repos import PerforcError
            raise PerforcError(output.errors)

        if output.changes:
            return max(output.changes)
        else:
            return None

    def next_rev(self, rev, path=''):

        # Finding the next revision is a little more difficult in Perforce
        # as we can only ask for the n most recent changes according to a
        # given criteria. We query batches of changes using a binary search
        # technique so that the number of changes queried is of the order of
        # log N where N is the number of changes greater than rev. This way
        # it is still fairly efficient if the next change is 1 or 1000 changes
        # later.

        self._log.debug('next_rev(%r,%r)' % (rev, path))

        from p4trac.repos import NodePath
        if not path:
            path = u'//'
        else:
            path = NodePath.normalisePath(path)
        node = self._repos.getNode(NodePath(path, rev))

        if node.isDirectory:
            if node.nodePath.isRoot:
                # Handle the root path specially since it encompasses all
                # changes and so can use the repository's internal cache.
                return self._repos.getNextChange(int(rev))
            else:
                queryPath = u'%s/...' % node.nodePath.path
        else:
            queryPath = node.nodePath.path

        queryPath = self._repos.fromUnicode(queryPath)

        self._log.debug(
            u'Looing for next_rev after change %i for %s' % (rev, path))

        # Perform a binary-search of sorts for the next revision
        batchSize = 50
        lowerBound = rev + 1
        upperBound = self.youngest_rev

        while lowerBound <= upperBound:

            if lowerBound + batchSize > upperBound:
                batchUpperBound = upperBound
            else:
                middle = (upperBound + lowerBound) / 2
                if middle - lowerBound < batchSize:
                    batchUpperBound = lowerBound + batchSize
                else:
                    batchUpperBound = middle

            self._log.debug(
                'Looking for changes in range [%i, %i]' % (lowerBound,
                                                           batchUpperBound))
                    
            from p4trac.repos import _P4ChangesOutputConsumer
            output = _P4ChangesOutputConsumer(self._repos)
            self._connection.run('changes', '-l', '-s', 'submitted',
                                 '-m', str(batchSize),
                                 '%s@>=%i,@<=%i' % (queryPath,
                                                    lowerBound,
                                                    batchUpperBound),
                                 output=output)

            if output.errors:
                from p4trac.repos import PerforcError
                raise PerforcError(output.errors)
                
            if output.changes:
                lowest = min(output.changes)
                assert lowest >= lowerBound
                assert lowest <= batchUpperBound

                if lowerBound + batchSize >= batchUpperBound:
                    # There are no earlier changes
                    self._log.debug('next_rev is %i' % lowest)
                    return lowest
                else:
                    # There may be another earlier changes but we know it
                    # can't be any later than lowest.
                    upperBound = lowest
            else:
                # Didn't find any changes in (lowerBound, batchUpperBound)
                # Try searching from batchUpperBound + 1 onwards
                lowerBound = batchUpperBound + 1

        return None

    def rev_older_than(self, rev1, rev2):

        self._log.debug('PerforceRepository.rev_older_than(%r,%r)' % (rev1,
                                                                      rev2))
        
        rev1 = self.short_rev(rev1)
        rev2 = self.short_rev(rev2)

        # Can compare equal revisions directly
        if rev1 == rev2:
            return False

        # Can compare change revisions directly
        if isinstance(rev1, int) and isinstance(rev2, int):
            return rev1 < rev2

        def parseDateRevision(rev):

            if not isinstance(rev, unicode):
                raise ValueError
            
            if not rev.startswith(u'@'):
                raise ValueError

            # @YYYY/MM/DD[:HH:MM:SS]
            if len(rev) not in [11, 20]:
                raise ValueError

            year = int(rev[1:5])
            month = int(rev[6:8])
            day = int(rev[9:11])

            if rev[5] != u'/' or rev[8] != u'/':
                raise ValueError

            if len(rev) == 20:
                hour = int(rev[12:14])
                minute = int(rev[15:17])
                second = int(rev[18:20])

                if rev[11] != u':' or rev[14] != u':' or rev[17] != u':':
                    raise ValueError
            else:
                hour = 0
                minute = 0
                second = 0

            return (year, month, day, hour, minute, second)

        # Can compare date revisions directly
        try:
            return parseDateRevision(rev1) < parseDateRevision(rev2)
        except ValueError:
            pass

        # Can't compare these revisions directly,
        # Compare based on the latest change number that affects this revision.
        from p4trac.repos import NodePath

        if not isinstance(rev1, int):
            rootAtRev1 = NodePath(u'//', rev1)
            rev1 = self._repos.getNode(rootAtRev1).change

        if not isinstance(rev2, int):
            rootAtRev2 = NodePath(u'//', rev2)
            rev2 = self._repos.getNode(rootAtRev2).change

        self._log.debug('Comparing by change rev1=%i, rev2=%i' % (rev1, rev2))

        return rev1 < rev2
        
    def get_youngest_rev_in_cache(self, db):

        cursor = db.cursor()
        cursor.execute("SELECT r.rev "
                       "FROM revision r "
                       "ORDER BY r.time DESC "
                       "LIMIT 1")
        row = cursor.fetchone()
        return row and row[0] or None

    def get_path_history(self, path, rev=None, limit=None):
        # TODO: This doesn't handle the case where the head node has been
        # deleted or a file has changed to a directory or vica versa.
        from p4trac.repos import NodePath
        nodePath = NodePath(NodePath.normalisePath(path), rev)
        node = PerforceNode(nodePath, self._repos, self._log)
        return node.get_history(limit)

    def normalize_path(self, path):
        self._log.debug('normalize_path(%r)' % path)
        return normalisePath(path)

    def normalize_rev(self, rev):
        self._log.debug('normalize_rev(%r)' % rev)
        rev = normaliseRev(rev)
        if rev is None:
            return self.youngest_rev
        else:
            return rev

    def short_rev(self, rev):
        self._log.debug('short_rev(%r)' % rev)
        return self.normalize_rev(rev)

    def get_changes(self, old_path, old_rev, new_path, new_rev,
                    ignore_ancestry=1):

        self._log.debug('PerforceRepository.get_changes(%r,%r,%r,%r)' % (
            old_path, old_rev, new_path, new_rev))

        from p4trac.repos import NodePath
        oldNodePath = NodePath(NodePath.normalisePath(old_path), old_rev)
        oldNode = self._repos.getNode(oldNodePath)

        newNodePath = NodePath(NodePath.normalisePath(new_path), new_rev)
        newNode = self._repos.getNode(newNodePath)


        if (newNode.isFile and oldNode.isDirectory) or \
           (newNode.isDirectory and oldNode.isFile):
            raise TracError("Cannot view changes between directory and file")

        if newNode.isDirectory or oldNode.isDirectory:

            if oldNodePath.isRoot:
                oldQueryPath = u'//...%s' % oldNodePath.rev
            else:
                oldQueryPath = u'%s/...%s' % (oldNodePath.path,
                                             oldNodePath.rev)

            if newNodePath.isRoot:
                newQueryPath = u'//...%s' % newNodePath.rev
            else:
                newQueryPath = u'%s/...%s' % (newNodePath.path,
                                             newNodePath.rev)

        elif newNode.isFile or oldNode.isFile:

            oldQueryPath = oldNodePath.fullPath
            newQueryPath = newNodePath.fullPath

        else:
            raise TracError("Cannot diff two non-existant nodes")

        from p4trac.repos import _P4Diff2OutputConsumer
        output = _P4Diff2OutputConsumer(self._repos)

        self._connection.run(
                'diff2', '-ds',
                self._repos.fromUnicode(oldQueryPath),
                self._repos.fromUnicode(newQueryPath),
                output=output)

        if output.errors:
            from p4trac.repos import PerforceError
            raise PerforceError(output.errors)

        for change in output.changes:

            oldFileNodePath, newFileNodePath = change

            if oldFileNodePath is not None:
                oldFileNode = PerforceNode(oldFileNodePath,
                                           self._repos,
                                           self._log)
            else:
                oldFileNode = None

            if newFileNodePath is not None:
                newFileNode = PerforceNode(newFileNodePath,
                                           self._repos,
                                           self._log)
            else:
                newFileNode = None

            if newFileNode and oldFileNode:
                yield (oldFileNode,
                       newFileNode,
                       Node.FILE,
                       Changeset.EDIT)
            elif newFileNode:
                yield (oldFileNode,
                       newFileNode,
                       Node.FILE,
                       Changeset.ADD)
            elif oldFileNode:
                yield (oldFileNode,
                       newFileNode,
                       Node.FILE,
                       Changeset.DELETE)
Esempio n. 2
0
class PerforceRepository(object):
    """A Perforce repository implementation.

    Built on top of the PyPerforce API.
    http://pyperforce.sourceforge.net/
    """

    def __init__(self, connection, log):

        self.authz = None

        # Log object for logging output
        self._log = log

        # The connection to the Perforce server
        self._connection = connection
        
        # The Repository object that we query for Perforce info
        from p4trac.repos import Repository
        self._repos = Repository(connection)

    def get_name(self):
        return 'p4://%s' % self._connection.port
    name = property(get_name)

    def close(self):
        self._connection.disconnect()

    def get_tags(self, rev):

        results = self._connection.run('labels')

        if results.errors:
            from p4trac.repos import PerforceError
            raise PerforceError(results.errors)

        for rec in results.records:
            name = self._repos.toUnicode(rec['label'])
            yield (name, u'@%s' % name)


    def get_branches(self, rev):
        # TODO: Generate a list of branches
        return []

    def get_changeset(self, rev):

        self._log.debug('get_changeset(%r)' % rev)

        if isinstance(rev, int):
            change = rev
        else:
            from p4trac.util import toUnicode
            rev = toUnicode(rev)
                
            if rev.startswith(u'@'):
                rev = rev[1:]
                
            try:
                change = int(rev)
            except ValueError:
                raise TracError(u"Invalid changeset number '%s'" % rev)
            
        return PerforceChangeset(change, self._repos, self._log)

    def get_changesets(self, start, stop):

        self._log.debug('PerforceRepository.get_changesets(%r,%r)' % (start,
                                                                      stop))

        import datetime
        start = datetime.datetime.fromtimestamp(start)
        stop = datetime.datetime.fromtimestamp(stop)

        startDate = start.strftime('%Y/%m/%d:%H:%M:%S')
        stopDate = stop.strftime('%Y/%m/%d:%H:%M:%S')

        from p4trac.repos import _P4ChangesOutputConsumer
        output = _P4ChangesOutputConsumer(self._repos)
        self._connection.run('changes', '-l', '-s', 'submitted',
                             '@>=%s,@<=%s' % (startDate, stopDate),
                             output=output)

        if output.errors:
            from p4trac.repos import PerforceError
            raise PerforceError(output.errors)

        for change in output.changes:
            yield self.get_changeset(change)

    def has_node(self, path, rev=None):
        from p4trac.repos import NodePath
        path = NodePath.normalisePath(path)
        return self._repos.getNode(NodePath(path, rev)).exists

    def get_node(self, path, rev=None):
        self._log.debug('get_node(%s, %s) called' % (path, rev))

        from p4trac.repos import NodePath
        nodePath = NodePath(NodePath.normalisePath(path), rev)
        
        return PerforceNode(nodePath, self._repos, self._log)

    def get_oldest_rev(self):
        return self.next_rev(0)
    oldest_rev = property(fget=get_oldest_rev)

    def get_youngest_rev(self):
        return self._repos.getLatestChange()
    youngest_rev = property(fget=get_youngest_rev)

    def previous_rev(self, rev):

        self._log.debug('previous_rev(%r)' % rev)

        if not isinstance(rev, int):
            rev = self.short_rev(rev)
            if not isinstance(rev, int):
                raise NoSuchChangeset(rev)

        from p4trac.repos import _P4ChangesOutputConsumer
        output = _P4ChangesOutputConsumer(self._repos)
        self._connection.run('changes', '-l', '-s', 'submitted',
                             '-m', '1',
                             '@<%i' % rev,
                             output=output)

        if output.errors:
            from p4trac.repos import PerforcError
            raise PerforcError(output.errors)

        if output.changes:
            return max(output.changes)
        else:
            return None

    def next_rev(self, rev, path=''):

        # Finding the next revision is a little more difficult in Perforce
        # as we can only ask for the n most recent changes according to a
        # given criteria. We query batches of changes using a binary search
        # technique so that the number of changes queried is of the order of
        # log N where N is the number of changes greater than rev. This way
        # it is still fairly efficient if the next change is 1 or 1000 changes
        # later.

        self._log.debug('next_rev(%r,%r)' % (rev, path))

        from p4trac.repos import NodePath
        if not path:
            path = u'//'
        else:
            path = NodePath.normalisePath(path)
        node = self._repos.getNode(NodePath(path, rev))

        if node.isDirectory:
            if node.nodePath.isRoot:
                # Handle the root path specially since it encompasses all
                # changes and so can use the repository's internal cache.
                return self._repos.getNextChange(int(rev))
            else:
                queryPath = u'%s/...' % node.nodePath.path
        else:
            queryPath = node.nodePath.path

        queryPath = self._repos.fromUnicode(queryPath)

        self._log.debug(
            u'Looing for next_rev after change %i for %s' % (rev, path))

        # Perform a binary-search of sorts for the next revision
        batchSize = 50
        lowerBound = rev + 1
        upperBound = self.youngest_rev

        while lowerBound <= upperBound:

            if lowerBound + batchSize > upperBound:
                batchUpperBound = upperBound
            else:
                middle = (upperBound + lowerBound) / 2
                if middle - lowerBound < batchSize:
                    batchUpperBound = lowerBound + batchSize
                else:
                    batchUpperBound = middle

            self._log.debug(
                'Looking for changes in range [%i, %i]' % (lowerBound,
                                                           batchUpperBound))
                    
            from p4trac.repos import _P4ChangesOutputConsumer
            output = _P4ChangesOutputConsumer(self._repos)
            self._connection.run('changes', '-l', '-s', 'submitted',
                                 '-m', str(batchSize),
                                 '%s@>=%i,@<=%i' % (queryPath,
                                                    lowerBound,
                                                    batchUpperBound),
                                 output=output)

            if output.errors:
                from p4trac.repos import PerforcError
                raise PerforcError(output.errors)
                
            if output.changes:
                lowest = min(output.changes)
                assert lowest >= lowerBound
                assert lowest <= batchUpperBound

                if lowerBound + batchSize >= batchUpperBound:
                    # There are no earlier changes
                    self._log.debug('next_rev is %i' % lowest)
                    return lowest
                else:
                    # There may be another earlier changes but we know it
                    # can't be any later than lowest.
                    upperBound = lowest
            else:
                # Didn't find any changes in (lowerBound, batchUpperBound)
                # Try searching from batchUpperBound + 1 onwards
                lowerBound = batchUpperBound + 1

        return None

    def rev_older_than(self, rev1, rev2):

        self._log.debug('PerforceRepository.rev_older_than(%r,%r)' % (rev1,
                                                                      rev2))
        
        rev1 = self.short_rev(rev1)
        rev2 = self.short_rev(rev2)

        # Can compare equal revisions directly
        if rev1 == rev2:
            return False

        # Can compare change revisions directly
        if isinstance(rev1, int) and isinstance(rev2, int):
            return rev1 < rev2

        def parseDateRevision(rev):

            if not isinstance(rev, unicode):
                raise ValueError
            
            if not rev.startswith(u'@'):
                raise ValueError

            # @YYYY/MM/DD[:HH:MM:SS]
            if len(rev) not in [11, 20]:
                raise ValueError

            year = int(rev[1:5])
            month = int(rev[6:8])
            day = int(rev[9:11])

            if rev[5] != u'/' or rev[8] != u'/':
                raise ValueError

            if len(rev) == 20:
                hour = int(rev[12:14])
                minute = int(rev[15:17])
                second = int(rev[18:20])

                if rev[11] != u':' or rev[14] != u':' or rev[17] != u':':
                    raise ValueError
            else:
                hour = 0
                minute = 0
                second = 0

            return (year, month, day, hour, minute, second)

        # Can compare date revisions directly
        try:
            return parseDateRevision(rev1) < parseDateRevision(rev2)
        except ValueError:
            pass

        # Can't compare these revisions directly,
        # Compare based on the latest change number that affects this revision.
        from p4trac.repos import NodePath

        if not isinstance(rev1, int):
            rootAtRev1 = NodePath(u'//', rev1)
            rev1 = self._repos.getNode(rootAtRev1).change

        if not isinstance(rev2, int):
            rootAtRev2 = NodePath(u'//', rev2)
            rev2 = self._repos.getNode(rootAtRev2).change

        self._log.debug('Comparing by change rev1=%i, rev2=%i' % (rev1, rev2))

        return rev1 < rev2
        
    def get_youngest_rev_in_cache(self, db):

        cursor = db.cursor()
        cursor.execute("SELECT r.rev "
                       "FROM revision r "
                       "ORDER BY r.time DESC "
                       "LIMIT 1")
        row = cursor.fetchone()
        return row and row[0] or None

    def get_path_history(self, path, rev=None, limit=None):
        # TODO: This doesn't handle the case where the head node has been
        # deleted or a file has changed to a directory or vica versa.
        from p4trac.repos import NodePath
        nodePath = NodePath(NodePath.normalisePath(path), rev)
        node = PerforceNode(nodePath, self._repos, self._log)
        return node.get_history(limit)

    def normalize_path(self, path):
        self._log.debug('normalize_path(%r)' % path)
        return normalisePath(path)

    def normalize_rev(self, rev):
        self._log.debug('normalize_rev(%r)' % rev)
        rev = normaliseRev(rev)
        if rev is None:
            return self.youngest_rev
        else:
            return rev

    def short_rev(self, rev):
        self._log.debug('short_rev(%r)' % rev)
        return self.normalize_rev(rev)

    def get_changes(self, old_path, old_rev, new_path, new_rev,
                    ignore_ancestry=1):

        self._log.debug('PerforceRepository.get_changes(%r,%r,%r,%r)' % (
            old_path, old_rev, new_path, new_rev))

        from p4trac.repos import NodePath
        oldNodePath = NodePath(NodePath.normalisePath(old_path), old_rev)
        oldNode = self._repos.getNode(oldNodePath)

        newNodePath = NodePath(NodePath.normalisePath(new_path), new_rev)
        newNode = self._repos.getNode(newNodePath)


        if (newNode.isFile and oldNode.isDirectory) or \
           (newNode.isDirectory and oldNode.isFile):
            raise TracError("Cannot view changes between directory and file")

        if newNode.isDirectory or oldNode.isDirectory:

            if oldNodePath.isRoot:
                oldQueryPath = u'//...%s' % oldNodePath.rev
            else:
                oldQueryPath = u'%s/...%s' % (oldNodePath.path,
                                             oldNodePath.rev)

            if newNodePath.isRoot:
                newQueryPath = u'//...%s' % newNodePath.rev
            else:
                newQueryPath = u'%s/...%s' % (newNodePath.path,
                                             newNodePath.rev)

        elif newNode.isFile or oldNode.isFile:

            oldQueryPath = oldNodePath.fullPath
            newQueryPath = newNodePath.fullPath

        else:
            raise TracError("Cannot diff two non-existant nodes")

        from p4trac.repos import _P4Diff2OutputConsumer
        output = _P4Diff2OutputConsumer(self._repos)

        self._connection.run(
                'diff2', '-ds',
                self._repos.fromUnicode(oldQueryPath),
                self._repos.fromUnicode(newQueryPath),
                output=output)

        if output.errors:
            from p4trac.repos import PerforceError
            raise PerforceError(output.errors)

        for change in output.changes:

            oldFileNodePath, newFileNodePath = change

            if oldFileNodePath is not None:
                oldFileNode = PerforceNode(oldFileNodePath,
                                           self._repos,
                                           self._log)
            else:
                oldFileNode = None

            if newFileNodePath is not None:
                newFileNode = PerforceNode(newFileNodePath,
                                           self._repos,
                                           self._log)
            else:
                newFileNode = None

            if newFileNode and oldFileNode:
                yield (oldFileNode,
                       newFileNode,
                       Node.FILE,
                       Changeset.EDIT)
            elif newFileNode:
                yield (oldFileNode,
                       newFileNode,
                       Node.FILE,
                       Changeset.ADD)
            elif oldFileNode:
                yield (oldFileNode,
                       newFileNode,
                       Node.FILE,
                       Changeset.DELETE)