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)
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)
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)
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)
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)
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)
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)
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 __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)
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)
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)
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)
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)
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))
def setUp(self): self.repo_base = Repository('testrepo', { 'name': 'testrepo', 'id': 1 }, None)