def get_changes(self): # Force population of the file history for the files modified in this # changelist. self._log.debug("PerforceChangeset(%i).get_changes()" % self._change) self._repos.precacheFileInformationForChanges([self._change]) for node in self._changelist.nodes: nodePath = node.nodePath self._log.debug( 'Change %i contains %s%s [%s]' % (self._change, nodePath.path, nodePath.rev, node.action)) if node.action in [u'add', u'branch', u'import']: if node.integrations: otherNodePath, how = node.integrations[0] otherNode = self._repos.getNode(otherNodePath) yield (normalisePath(nodePath.path), Node.FILE, Changeset.COPY, normalisePath(otherNodePath.path), otherNode.change) else: yield (normalisePath(nodePath.path), Node.FILE, Changeset.ADD, None, None) elif node.action in [u'edit', u'integrate']: if node.integrations and node.integrations[0][1] == 'copy': otherNodePath, how = node.integrations[0] otherNode = self._repos.getNode(otherNodePath) # A 'copy from' operation yield (normalisePath(nodePath.path), Node.FILE, Changeset.COPY, normalisePath(otherNodePath.path), otherNode.change) else: if node.fileRevision > 1: from p4trac.repos import P4NodePath otherNode = self._repos.getNode( P4NodePath(nodePath.path, '#%i' % (node.fileRevision - 1))) # A basic edit operation yield (normalisePath(nodePath.path), Node.FILE, Changeset.EDIT, normalisePath(nodePath.path), otherNode.change) else: yield (normalisePath(nodePath.path), Node.FILE, Changeset.EDIT, None, None) elif node.action in [u'delete']: # The file was deleted from p4trac.repos import P4NodePath otherNodePath = P4NodePath(nodePath.path, '#%i' % (node.fileRevision - 1)) otherNode = self._repos.getNode(otherNodePath) yield (normalisePath(nodePath.path), Node.FILE, Changeset.DELETE, normalisePath(nodePath.path), otherNode.change)
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 P4NodePath nodePath = P4NodePath(P4NodePath.normalisePath(path), rev) node = PerforceNode(nodePath, self._repos, self.log) return node.get_history(limit)
def normaliseRev(rev): """Normalise a Perforce revision and return it as a Trac-compatible rev. Basically converts revisions to '@<label/client/date>' or '#<rev>' but returns '@<change>' as an integer value. """ from p4trac.repos import P4NodePath rev = P4NodePath.normaliseRevision(rev) if rev is None: return rev elif rev.startswith(u'@') and rev[1:].isdigit(): return int(rev[1:]) else: return rev
def normalisePath(path): """Normalise a Perforce path and return it as a Trac-compatible path. If None or the empty string is passed then the root path is returned. The path is returned with a single leading slash rather than the Perforce depot notation which uses two leading slashes. @return: The normalised Perforce path. @rtype: C{unicode} """ from p4trac.repos import P4NodePath path = P4NodePath.normalisePath(path) if path is None: return u'/' else: return path[1:]
def get_changes(self, old_path, old_rev, new_path, new_rev, ignore_ancestry=0): self.log.debug('PerforceRepository.get_changes(%r,%r,%r,%r)' % ( old_path, old_rev, new_path, new_rev)) from p4trac.repos import P4NodePath oldNodePath = P4NodePath(P4NodePath.normalisePath(old_path), old_rev) oldNode = self._repos.getNode(oldNodePath) newNodePath = P4NodePath(P4NodePath.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...%s' % (rootPath(self._connection), oldNodePath.rev) else: oldQueryPath = u'%s/...%s' % (oldNodePath.path, oldNodePath.rev) if newNodePath.isRoot: newQueryPath = u'%s...%s' % (rootPath(self._connection), 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.log.debug('p4 diff2 -ds %s %s' % (oldQueryPath, newQueryPath)) 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
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 P4NodePath if not path: queryPath = u'...' else: path = P4NodePath.normalisePath(path) node = self._repos.getNode(P4NodePath(path, rev)) self.log.debug(u'node : %i %i %s' % (node.isDirectory, node.nodePath.isRoot, node.nodePath.path)) if node.isDirectory: if node.nodePath.isRoot: queryPath = u'...' else: queryPath = u'%s/...' % node.nodePath.path else: queryPath = node.nodePath.path queryPath = self._repos.fromUnicode(queryPath) self.log.debug(u'Looking 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) depot_path = '%s%s@>=%i,@<=%i' % (rootPath(self._connection), queryPath, lowerBound, batchUpperBound) self.log.debug(u'p4 changes -l -s submitted -m %s %s' % (batchSize, depot_path)) self._connection.run('changes', '-l', '-s', 'submitted', '-m', str(batchSize), depot_path, output=output) if output.errors: from p4trac.repos import PerforceError raise PerforceError(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 get_node(self, path, rev=None): self.log.debug('get_node(%s, %s) called' % (path, rev)) from p4trac.repos import P4NodePath nodePath = P4NodePath(P4NodePath.normalisePath(path), rev) return PerforceNode(nodePath, self._repos, self.log)
def has_node(self, path, rev=None): from p4trac.repos import P4NodePath path = P4NodePath.normalisePath(path) return self._repos.getNode(P4NodePath(path, rev)).exists
def get_history(self, limit=None): self._log.debug('PerforceNode.get_history(%r)' % limit) if self._node.isFile: # Force population of the filelog history for efficiency self._repos._runFileLog(self._nodePath, limit) from p4trac.repos import P4NodePath currentNode = self._node i = 0 while i < limit and currentNode is not None: if currentNode.action in [u'add', u'branch', u'import']: if currentNode.integrations: nodePath, how = currentNode.integrations[0] # TODO: Detect whether the copy was really a move yield (normalisePath(currentNode.nodePath.path), currentNode.change, Changeset.COPY) currentNode = self._repos.getNode(nodePath) else: yield (normalisePath(currentNode.nodePath.path), currentNode.change, Changeset.ADD) if currentNode.fileRevision > 1: # Get the previous revision nodePath = P4NodePath( currentNode.nodePath.path, '#%i' % (currentNode.fileRevision - 1)) currentNode = self._repos.getNode(nodePath) else: currentNode = None elif currentNode.action in [u'edit', u'integrate']: nextNode = None if currentNode.integrations: nodePath, how = currentNode.integrations[0] if how == 'copy': yield (normalisePath(currentNode.nodePath.path), currentNode.change, Changeset.COPY) nextNode = self._repos.getNode(nodePath) else: yield (normalisePath(currentNode.nodePath.path), currentNode.change, Changeset.EDIT) else: yield (normalisePath(currentNode.nodePath.path), currentNode.change, Changeset.EDIT) if nextNode is None: if currentNode.fileRevision > 1: currentNode = self._repos.getNode( P4NodePath( currentNode.nodePath.path, '#%i' % (currentNode.fileRevision - 1))) else: currentNode = None else: currentNode = nextNode elif currentNode.action in [u'delete']: yield (normalisePath(currentNode.nodePath.path), currentNode.change, Changeset.DELETE) if currentNode.fileRevision > 1: currentNode = self._repos.getNode( P4NodePath(currentNode.nodePath.path, '#%i' % (currentNode.fileRevision - 1))) else: currentNode = None i += 1 elif self._node.isDirectory: # List all changelists that have affected this directory from p4trac.repos import P4ChangesOutputConsumer output = P4ChangesOutputConsumer(self._repos) if self._nodePath.isRoot: queryPath = '%s%s' % (rootPath( self._repos._connection), self._nodePath.rev) else: queryPath = '%s/...%s' % (self._nodePath.path, self._nodePath.rev) if limit is None: self._repos._connection.run('changes', '-l', '-s', 'submitted', self._repos.fromUnicode(queryPath), output=output) else: self._repos._connection.run('changes', '-l', '-s', 'submitted', '-m', str(limit), self._repos.fromUnicode(queryPath), output=output) if output.errors: raise PerforceError(output.errors) changes = output.changes self._repos._runDescribe(changes) from p4trac.repos import P4NodePath for i in xrange(len(changes)): change = changes[i] nodePath = P4NodePath(self._nodePath.path, change) if i < len(changes) - 1: prevChange = changes[i + 1] else: prevChange = change - 1 prevNodePath = P4NodePath(self._nodePath.path, prevChange) node = self._repos.getNode(nodePath) prevNode = self._repos.getNode(prevNodePath) if node.isDirectory: if prevNode.isDirectory: yield (normalisePath(self._nodePath.path), change, Changeset.EDIT) else: yield (normalisePath(self._nodePath.path), change, Changeset.ADD) elif prevNode.isDirectory: yield (normalisePath(self._nodePath.path), change, Changeset.DELETE) else: raise NoSuchNode(self._nodePath.path, self._nodePath.rev)
def get_changes(self, old_path, old_rev, new_path, new_rev, ignore_ancestry=0): self.log.debug('PerforceRepository.get_changes(%r,%r,%r,%r)' % (old_path, old_rev, new_path, new_rev)) from p4trac.repos import P4NodePath oldNodePath = P4NodePath(P4NodePath.normalisePath(old_path), old_rev) oldNode = self._repos.getNode(oldNodePath) newNodePath = P4NodePath(P4NodePath.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%s' % (rootPath( self._connection), oldNodePath.rev) else: oldQueryPath = u'%s/...%s' % (oldNodePath.path, oldNodePath.rev) if newNodePath.isRoot: newQueryPath = u'%s%s' % (rootPath( self._connection), 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, self.log) else: oldFileNode = None if newFileNodePath is not None: newFileNode = PerforceNode(newFileNodePath, self, 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
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 P4NodePath if not path: path = u'//' else: path = P4NodePath.normalisePath(path) node = self._repos.getNode(P4NodePath(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'Looking 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) depot_path = '%s%s@>=%i,@<=%i' % (rootPath( self._connection), queryPath, lowerBound, batchUpperBound) self._connection.run('changes', '-l', '-s', 'submitted', '-m', str(batchSize), depot_path, output=output) if output.errors: from p4trac.repos import PerforceError raise PerforceError(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 get_node(self, path, rev=None): self.log.debug('get_node(%s, %s) called' % (path, rev)) from p4trac.repos import P4NodePath nodePath = P4NodePath(P4NodePath.normalisePath(path), rev) return PerforceNode(nodePath, self, self.log)