Esempio n. 1
0
    def __init__(self, env, path, params, log,
                 persistent_cache=False,
                 git_bin='git',
                 git_fs_encoding='utf-8',
                 shortrev_len=7,
                 rlookup_uid=lambda _: None,
                 use_committer_id=False,
                 use_committer_time=False,
                 ):

        self.env = env
        self.logger = log
        self.gitrepo = path
        self.params = params
        self.persistent_cache = persistent_cache
        self.shortrev_len = max(4, min(shortrev_len, 40))
        self.rlookup_uid = rlookup_uid
        self.use_committer_time = use_committer_time
        self.use_committer_id = use_committer_id

        try:
            factory = PyGIT.StorageFactory(path, log, not persistent_cache,
                                           git_bin=git_bin,
                                           git_fs_encoding=git_fs_encoding)
            self._git = factory.getInstance()
        except PyGIT.GitError as e:
            log.error(exception_to_unicode(e))
            raise InvalidRepository(
                _("%(path)s does not appear to be a Git repository.",
                  path=path))

        Repository.__init__(self, 'git:' + path, self.params, log)
        self._cached_git_id = str(self.id)
Esempio n. 2
0
 def __init__(self, path, params, env, log):
     self.path = path
     self.env = env
     if not os.path.exists(self.env.path + '/cvsntplugin'):
         os.mkdir(self.env.path + '/cvsntplugin')
     self.database_name = self.env.path + '/cvsntplugin/changesets.db'
     Repository.__init__(self, path, params, log)
Esempio n. 3
0
    def __init__(
        self,
        path,
        params,
        log,
        persistent_cache=False,
        git_bin='git',
        git_fs_encoding='utf-8',
        shortrev_len=7,
        rlookup_uid=lambda _: None,
        use_committer_id=False,
        use_committer_time=False,
    ):

        self.logger = log
        self.gitrepo = path
        self.params = params
        self._shortrev_len = max(4, min(shortrev_len, 40))
        self.rlookup_uid = rlookup_uid
        self._use_committer_time = use_committer_time
        self._use_committer_id = use_committer_id

        self.git = PyGIT.StorageFactory(
            path,
            log,
            not persistent_cache,
            git_bin=git_bin,
            git_fs_encoding=git_fs_encoding).getInstance()

        Repository.__init__(self, "git:" + path, self.params, log)
Esempio n. 4
0
    def __init__(self, env, path, params, log,
                 persistent_cache=False,
                 git_bin='git',
                 git_fs_encoding='utf-8',
                 shortrev_len=7,
                 rlookup_uid=lambda _: None,
                 use_committer_id=False,
                 use_committer_time=False,
                 ):

        self.env = env
        self.logger = log
        self.gitrepo = path
        self.params = params
        self.persistent_cache = persistent_cache
        self.shortrev_len = max(4, min(shortrev_len, 40))
        self.rlookup_uid = rlookup_uid
        self.use_committer_time = use_committer_time
        self.use_committer_id = use_committer_id

        try:
            factory = PyGIT.StorageFactory(path, log, not persistent_cache,
                                           git_bin=git_bin,
                                           git_fs_encoding=git_fs_encoding)
            self._git = factory.getInstance()
        except PyGIT.GitError as e:
            log.error(exception_to_unicode(e))
            raise InvalidRepository(
                _("%(path)s does not appear to be a Git repository.",
                  path=path))

        Repository.__init__(self, 'git:' + path, self.params, log)
        self._cached_git_id = str(self.id)
Esempio n. 5
0
 def __init__(self, path, params, env, log):
     self.path = path
     self.env = env
     if not os.path.exists (self.env.path + '/cvsntplugin'):
         os.mkdir(self.env.path + '/cvsntplugin')
     self.database_name = self.env.path + '/cvsntplugin/changesets.db'
     Repository.__init__(self, path, params, log)
Esempio n. 6
0
    def __init__(self, path, params, log,
                 persistent_cache=False,
                 git_bin='git',
                 git_fs_encoding='utf-8',
                 shortrev_len=7,
                 rlookup_uid=lambda _: None,
                 use_committer_id=False,
                 use_committer_time=False,
                 ):

        self.logger = log
        self.gitrepo = path
        self.params = params
        self._shortrev_len = max(4, min(shortrev_len, 40))
        self.rlookup_uid = rlookup_uid
        self._use_committer_time = use_committer_time
        self._use_committer_id = use_committer_id

        _use_svn_id = BoolOption('git', 'use_svn_id', 'false',
                                 "try looking up revisions by git-svn-id if present")

        self.git = PyGIT.StorageFactory(path, log, not persistent_cache,
                                        git_bin=git_bin,
                                        git_fs_encoding=git_fs_encoding,
                                        was_svn=_use_svn_id).getInstance()

        Repository.__init__(self, "git:"+path, self.params, log)
Esempio n. 7
0
        def __init__(self, path, log, persistent_cache=False, git_bin='git', shortrev_len=7):
                self.logger = log
                self.gitrepo = path
                self._shortrev_len = max(4, min(shortrev_len, 40))

                self.git = PyGIT.StorageFactory(path, log, not persistent_cache,
                                                git_bin=git_bin).getInstance()
                Repository.__init__(self, "git:"+path, None, log)
Esempio n. 8
0
    def __init__(self, connection_string, root_store_bundle, root_store_package, authz, log):
        """Initialize the repository.

        This call creates the database connection.  repos_name is expected to be a valid database
        connection string.  One of root_store_bundle and root_store_package must specify what to
        consider as the root of the Store repository view: if neither are provided, the full repository
        will be visible.
        """
        Repository.__init__(self, connection_string, authz, log)
        self.connection = pgdb.connect(connection_string)
        self.root = RootNode(self, root_store_bundle, root_store_package)
Esempio n. 9
0
    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)
Esempio n. 10
0
    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)
Esempio n. 11
0
class GitRepository(Repository):
    """Git repository"""
    def __init__(
        self,
        path,
        params,
        log,
        persistent_cache=False,
        git_bin='git',
        git_fs_encoding='utf-8',
        shortrev_len=7,
        rlookup_uid=lambda _: None,
        use_committer_id=False,
        use_committer_time=False,
    ):

        self.logger = log
        self.gitrepo = path
        self.params = params
        self.shortrev_len = max(4, min(shortrev_len, 40))
        self.rlookup_uid = rlookup_uid
        self.use_committer_time = use_committer_time
        self.use_committer_id = use_committer_id

        try:
            self.git = PyGIT.StorageFactory(path, log, not persistent_cache,
                                            git_bin=git_bin,
                                            git_fs_encoding=git_fs_encoding) \
                            .getInstance()
        except PyGIT.GitError, e:
            raise TracError("%s does not appear to be a Git "
                            "repository." % path)

        Repository.__init__(self, 'git:' + path, self.params, log)
Esempio n. 12
0
    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)
Esempio n. 13
0
    def __init__(self, path, params, log,
                 persistent_cache=False,
                 git_bin='git',
                 git_fs_encoding='utf-8',
                 shortrev_len=7,
                 rlookup_uid=lambda _: None,
                 use_committer_id=False,
                 use_committer_time=False,
                 ):

        self.logger = log
        self.gitrepo = path
        self.params = params
        self._shortrev_len = max(4, min(shortrev_len, 40))
        self.rlookup_uid = rlookup_uid
        self._use_committer_time = use_committer_time
        self._use_committer_id = use_committer_id

        self.git = PyGIT.StorageFactory(path, log, not persistent_cache,
                                        git_bin=git_bin,
                                        git_fs_encoding=git_fs_encoding).getInstance()

        Repository.__init__(self, "git:"+path, self.params, log)
Esempio n. 14
0
    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)
Esempio n. 15
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. 16
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)
Esempio n. 17
0
class TestMultiProjectCommitTicketUpdater(unittest.TestCase):
    # Component architecture plumbing - load environments and components
    # under test (check that each environment matches the right tickets)
    comp_mgr1 = trac.env.open_environment('./testenv/')
    comp_mgr2 = trac.env.open_environment('./testenv2/')
    obj1 = MultiProjectCommitTicketUpdater(comp_mgr1)
    obj2 = MultiProjectCommitTicketUpdater(comp_mgr2)
    # Create fake repository for changesets (note that both fake projects
    # need to use the same fake repository)
    test_repo = Repository("Test_repo", {
        'name': "Test_repo",
        'id': 4321
    }, "tmp.log")

    def setUp(self):
        # Set all component objects to defaults
        self.obj1.config.set("multicommitupdater", "envelope", "")
        self.obj2.config.set("multicommitupdater", "envelope", "")
        self.obj1.config.set("multicommitupdater", "commands.close",
                             "close closed closes fix fixed fixes")
        self.obj2.config.set("multicommitupdater", "commands.close",
                             "close closed closes fix fixed fixes")
        self.obj1.config.set("multicommitupdater", "commands.refs",
                             "addresses re references refs see")
        self.obj2.config.set("multicommitupdater", "commands.refs",
                             "addresses re references refs see")

    def test_simple_case(self):
        message = "Fixed some stuff. Re test:#1, closes test2:#2"
        test_changeset = Changeset(None, 1234, message, "test_person",
                                   time.time())
        self.check_ticket_comment(test_changeset)
        # For each object in turn:
        # Get tickets and commands
        tickets = self.obj1._parse_message(message)
        # First, check we've got the tickets we were expecting
        self.assertEqual(tickets.keys(), [1])
        # Now check the actions are right
        self.assertEqual(tickets.get(1), [self.obj1.cmd_refs])
        # Same checks for obj2:
        tickets = self.obj2._parse_message(message)
        self.assertEqual(tickets.keys(), [2])
        self.assertEqual(tickets.get(2), [self.obj2.cmd_close])

    def test_erroneous_capital_letters(self):
        message = "Did some more stuff. Fixes Test:#3, refs test2:#4"
        test_changeset2 = Changeset(None, 1235, message, "test_person",
                                    time.time())
        self.check_ticket_comment(test_changeset2)

        tickets = self.obj1._parse_message(message)
        self.assertEqual(tickets.keys(), [3])
        self.assertEqual(tickets.get(3), [self.obj1.cmd_close])

        tickets = self.obj2._parse_message(message)
        self.assertEqual(tickets.keys(), [4])
        self.assertEqual(tickets.get(4), [self.obj2.cmd_refs])

    def test_all_documented_synonyms_close(self):
        message = "More things. close test:#5, closed test2:#6, closes " + \
            "test:#7, fix test2:#8, fixed test:#9, fixes test2:#10."
        test_changeset3 = Changeset(None, 1236, message, "test_person",
                                    time.time())
        self.check_ticket_comment(test_changeset3)

        tickets = self.obj1._parse_message(message)
        sorted_keys = tickets.keys()
        sorted_keys.sort()
        self.assertEqual(sorted_keys, [5, 7, 9])
        self.assertEqual(tickets.get(5), [self.obj1.cmd_close])
        self.assertEqual(tickets.get(7), [self.obj1.cmd_close])
        self.assertEqual(tickets.get(9), [self.obj1.cmd_close])

        tickets = self.obj2._parse_message(message)
        sorted_keys = tickets.keys()
        sorted_keys.sort()
        self.assertEqual(sorted_keys, [6, 8, 10])
        self.assertEqual(tickets.get(6), [self.obj2.cmd_close])
        self.assertEqual(tickets.get(8), [self.obj2.cmd_close])
        self.assertEqual(tickets.get(10), [self.obj2.cmd_close])

    def test_all_documented_synonyms_ref(self):
        message = "Yet more things. references test:#11, refs test2:#12, " + \
            "addresses test:#13, re test2:#14, see test:#15"
        test_changeset4 = Changeset(None, 1237, message, "test_person",
                                    time.time())
        self.check_ticket_comment(test_changeset4)

        tickets = self.obj1._parse_message(message)
        sorted_keys = tickets.keys()
        sorted_keys.sort()
        self.assertEqual(sorted_keys, [11, 13, 15])
        self.assertEqual(tickets.get(11), [self.obj1.cmd_refs])
        self.assertEqual(tickets.get(13), [self.obj1.cmd_refs])
        self.assertEqual(tickets.get(15), [self.obj1.cmd_refs])

        tickets = self.obj2._parse_message(message)
        sorted_keys = tickets.keys()
        sorted_keys.sort()
        self.assertEqual(sorted_keys, [12, 14])
        self.assertEqual(tickets.get(12), [self.obj2.cmd_refs])
        self.assertEqual(tickets.get(14), [self.obj2.cmd_refs])

# TODO: def test_all_synonyms_ticket(self):

    def test_multiple_tickets_one_command_simple(self):
        message = "And even more things. re test:#16,#17,#18. Closes " + \
            "test2:#19,#20"
        test_changeset5 = Changeset(None, 1238, message, "test_person",
                                    time.time())
        self.check_ticket_comment(test_changeset5)

        tickets = self.obj1._parse_message(message)
        sorted_keys = tickets.keys()
        sorted_keys.sort()
        self.assertEqual(sorted_keys, [16, 17, 18])
        self.assertEqual(tickets.get(16), [self.obj1.cmd_refs])
        self.assertEqual(tickets.get(17), [self.obj1.cmd_refs])
        self.assertEqual(tickets.get(18), [self.obj1.cmd_refs])

        tickets = self.obj2._parse_message(message)
        sorted_keys = tickets.keys()
        sorted_keys.sort()
        self.assertEqual(sorted_keys, [19, 20])
        self.assertEqual(tickets.get(19), [self.obj2.cmd_close])
        self.assertEqual(tickets.get(20), [self.obj2.cmd_close])

    def test_multiple_tickets_one_command_complex(self):
        message = "Woo, even more things. Look at us go! Refs test:#21 & " + \
            "#22, closes test:#23. See test2:#24,#25 and #26."
        test_changeset6 = Changeset(None, 1239, message, "test_person",
                                    time.time())
        self.check_ticket_comment(test_changeset6)

        tickets = self.obj1._parse_message(message)
        sorted_keys = tickets.keys()
        sorted_keys.sort()
        self.assertEqual(sorted_keys, [21, 22, 23])
        self.assertEqual(tickets.get(21), [self.obj1.cmd_refs])
        self.assertEqual(tickets.get(22), [self.obj1.cmd_refs])
        self.assertEqual(tickets.get(23), [self.obj1.cmd_close])

        tickets = self.obj2._parse_message(message)
        sorted_keys = tickets.keys()
        sorted_keys.sort()
        self.assertEqual(sorted_keys, [24, 25, 26])
        self.assertEqual(tickets.get(24), [self.obj2.cmd_refs])
        self.assertEqual(tickets.get(25), [self.obj2.cmd_refs])
        self.assertEqual(tickets.get(26), [self.obj2.cmd_refs])

    def test_one_env(self):
        message = "These things are only applicable to test2. See test" + \
            "2:#27, closes test2:#28"
        test_changeset7 = Changeset(None, 1240, message, "test_person",
                                    time.time())
        self.check_ticket_comment(test_changeset7)

        tickets = self.obj1._parse_message(message)
        self.assertEqual(tickets, {})

        tickets = self.obj2._parse_message(message)
        sorted_keys = tickets.keys()
        sorted_keys.sort()
        self.assertEqual(sorted_keys, [27, 28])
        self.assertEqual(tickets.get(27), [self.obj2.cmd_refs])
        self.assertEqual(tickets.get(28), [self.obj2.cmd_close])

    def test_spaces(self):
        message = "Today I am feeling spacey! Closes test:#29, #30. See " + \
            "test2:#31"
        test_changeset8 = Changeset(None, 1241, message, "test_person",
                                    time.time())
        self.check_ticket_comment(test_changeset8)

        tickets = self.obj1._parse_message(message)
        sorted_keys = tickets.keys()
        sorted_keys.sort()
        self.assertEqual(sorted_keys, [29, 30])
        self.assertEqual(tickets.get(29), [self.obj1.cmd_close])
        self.assertEqual(tickets.get(30), [self.obj1.cmd_close])

        tickets = self.obj2._parse_message(message)
        self.assertEqual(tickets.keys(), [31])
        self.assertEqual(tickets.get(31), [self.obj2.cmd_refs])

    #
    # Tests of other documented committicketupdater functionality
    #
    def test_envelope(self):
        message = "Today, I shall be putting my messages in an envelope. " + \
            "[re test:#32], [re test2:#33]"
        test_changeset9 = Changeset(None, 1242, message, "test_person",
                                    time.time())
        self.check_ticket_comment(test_changeset9)

        # With no envelope set, this works as normal
        tickets = self.obj1._parse_message(message)
        self.assertEqual(tickets.keys(), [32])
        self.assertEqual(tickets.get(32), [self.obj1.cmd_refs])

        tickets = self.obj2._parse_message(message)
        self.assertEqual(tickets.keys(), [33])
        self.assertEqual(tickets.get(33), [self.obj2.cmd_refs])

        # With a different envelope set, the messages should be ignored
        self.obj1.config.set("multicommitupdater", "envelope", "{}")
        self.obj2.config.set("multicommitupdater", "envelope", "<>")

        tickets = self.obj1._parse_message(message)
        self.assertEqual(tickets.keys(), [])

        tickets = self.obj2._parse_message(message)
        self.assertEqual(tickets.keys(), [])

        # Changing one envelope to '[]' should mean only that environment
        # picks up the message
        self.obj1.config.set("multicommitupdater", "envelope", "[]")

        tickets = self.obj1._parse_message(message)
        self.assertEqual(tickets.keys(), [32])
        self.assertEqual(tickets.get(32), [self.obj1.cmd_refs])

        tickets = self.obj2._parse_message(message)
        self.assertEqual(tickets.keys(), [])

        # When both envelopes are '[]', both environments pick up the actions
        self.obj2.config.set("multicommitupdater", "envelope", "[]")
        tickets = self.obj1._parse_message(message)
        self.assertEqual(tickets.keys(), [32])
        self.assertEqual(tickets.get(32), [self.obj1.cmd_refs])

        tickets = self.obj2._parse_message(message)
        self.assertEqual(tickets.keys(), [33])
        self.assertEqual(tickets.get(33), [self.obj2.cmd_refs])

    def test_custom_commands_close(self):
        message = "this time, we're going to use some close commands that " + \
            "aren't on the default list. Nukes test:#34, #35 and " + \
            "obliterates test2:#36"
        test_changeset10 = Changeset(None, 1243, message, "test_person",
                                     time.time())
        self.check_ticket_comment(test_changeset10)

        # Nothing happens if we stick with the default commands
        tickets = self.obj1._parse_message(message)
        self.assertEqual(tickets.keys(), [])

        tickets = self.obj2._parse_message(message)
        self.assertEqual(tickets.keys(), [])

        # Adding 'nukes' to the list should make our first command work
        self.obj1.config.set("multicommitupdater", "commands.close",
                             "ends nukes completes")
        self.obj2.config.set("multicommitupdater", "commands.close",
                             "ends nukes completes")

        tickets = self.obj1._parse_message(message)
        sorted_keys = tickets.keys()
        sorted_keys.sort()
        self.assertEqual(sorted_keys, [34, 35])
        self.assertEqual(tickets.get(34), [self.obj1.cmd_close])
        self.assertEqual(tickets.get(35), [self.obj1.cmd_close])

        tickets = self.obj2._parse_message(message)
        self.assertEqual(tickets.keys(), [])

        # And adding 'obliterates' should take care of the second
        self.obj1.config.set("multicommitupdater", "commands.close",
                             "ends nukes obliterates completes")
        self.obj2.config.set("multicommitupdater", "commands.close",
                             "ends nukes obliterates completes")

        tickets = self.obj1._parse_message(message)
        sorted_keys = tickets.keys()
        sorted_keys.sort()
        self.assertEqual(sorted_keys, [34, 35])
        self.assertEqual(tickets.get(34), [self.obj1.cmd_close])
        self.assertEqual(tickets.get(35), [self.obj1.cmd_close])

        tickets = self.obj2._parse_message(message)
        self.assertEqual(tickets.keys(), [36])
        self.assertEqual(tickets.get(36), [self.obj2.cmd_close])

        # Sad path test
        message = "now we're checking that tickets aren't closed by " + \
            "default commands when the list has been overwritten. " + \
            "closes test:#37, fixes test2:#38"
        test_changeset11 = Changeset(None, 1244, message, "test_person",
                                     time.time())
        self.check_ticket_comment(test_changeset11)

        tickets = self.obj1._parse_message(message)
        self.assertEqual(tickets.keys(), [])

        tickets = self.obj2._parse_message(message)
        self.assertEqual(tickets.keys(), [])

    def test_custom_commands_refs(self):
        message = "And now something very similar wth refs. Lookit test:#39" + \
            "affects test2:#40 & #41"
        test_changeset12 = Changeset(None, 1245, message, "test_person",
                                     time.time())
        self.check_ticket_comment(test_changeset12)

        tickets = self.obj1._parse_message(message)
        self.assertEqual(tickets.keys(), [])

        tickets = self.obj2._parse_message(message)
        self.assertEqual(tickets.keys(), [])

        self.obj1.config.set("multicommitupdater", "commands.refs",
                             "relevant-to lookit affects related")
        self.obj2.config.set("multicommitupdater", "commands.refs",
                             "relevant-to lookit affects related")

        tickets = self.obj1._parse_message(message)
        self.assertEqual(tickets.keys(), [39])
        self.assertEqual(tickets.get(39), [self.obj1.cmd_refs])

        tickets = self.obj2._parse_message(message)
        sorted_keys = tickets.keys()
        sorted_keys.sort()
        self.assertEqual(sorted_keys, [40, 41])
        self.assertEqual(tickets.get(40), [self.obj2.cmd_refs])
        self.assertEqual(tickets.get(41), [self.obj2.cmd_refs])

        # :(
        message = "can we still ref tickets using one of the old " + \
            "commands? re test:#42, re test2:#43"
        test_changeset13 = Changeset(None, 1246, message, "test_person",
                                     time.time())
        self.check_ticket_comment(test_changeset13)

        tickets = self.obj1._parse_message(message)
        self.assertEqual(tickets.keys(), [])

        tickets = self.obj2._parse_message(message)
        self.assertEqual(tickets.keys(), [])

#------------------------------------------------------------------------------
# Utilities

    def build_comment(self, changeset):
        return """In [%d/%s]:
{{{
#!CommitTicketReference repository="%s" revision="%d"
%s
}}}""" % (changeset.rev, self.test_repo.name, self.test_repo.name, \
                                 changeset.rev, changeset.message)

    def check_ticket_comment(self, changeset):
        for obj in [self.obj1, self.obj2]:
            self.assertEqual(
                obj.make_ticket_comment(self.test_repo, changeset),
                self.build_comment(changeset))
Esempio n. 18
0
 def setUp(self):
     self.repo_base = Repository('testrepo', {
         'name': 'testrepo',
         'id': 1
     }, None)