Edgewall Software

Ticket #1930: scoped_get_path_history.patch

File scoped_get_path_history.patch, 15.6 KB (added by cboos, 3 years ago)

get_path_history now takes into account the repository scope

  • trac/versioncontrol/api.py

     
    3939        """ 
    4040        raise NotImplementedError 
    4141 
     42    def has_node(self, path, rev): 
     43        """ 
     44        Tell if there's a node at the specified (path,rev) combination. 
     45        """ 
     46        raise NotImplementedError 
     47     
    4248    def get_node(self, path, rev=None): 
    4349        """ 
    4450        Retrieve a Node (directory or file) from the repository at the 
     
    149155        node (if the underlying version control system supports that), which 
    150156        will be indicated by the first element of the tuple (i.e. the path) 
    151157        changing. 
     158        Starts with an entry for the current revision. 
    152159        """ 
    153160        raise NotImplementedError 
    154161 
  • trac/versioncontrol/tests/svn_fs.py

     
    109109        # ... 
    110110        self.assertEqual(None, self.repos.next_rev(12)) 
    111111 
     112    def test_has_node(self): 
     113        self.assertEqual(False, self.repos.has_node('/trunk/dir1', 3)) 
     114        self.assertEqual(True, self.repos.has_node('/trunk/dir1', 4)) 
     115         
    112116    def test_get_node(self): 
    113117        node = self.repos.get_node('/trunk') 
    114118        self.assertEqual('trunk', node.name) 
     
    278282        self.assertRaises(StopIteration, changes.next) 
    279283 
    280284 
     285class ScopedSubversionRepositoryTestCase(unittest.TestCase): 
     286 
     287    def setUp(self): 
     288        self.repos = SubversionRepository(REPOS_PATH + '/trunk', None, 
     289                                          logger_factory('test')) 
     290 
     291    def tearDown(self): 
     292        self.repos = None 
     293 
     294    def test_repos_normalize_path(self): 
     295        self.assertEqual('/', self.repos.normalize_path('/')) 
     296        self.assertEqual('/', self.repos.normalize_path('')) 
     297        self.assertEqual('/', self.repos.normalize_path(None)) 
     298        self.assertEqual('dir1', self.repos.normalize_path('dir1')) 
     299        self.assertEqual('dir1', self.repos.normalize_path('/dir1')) 
     300        self.assertEqual('dir1', self.repos.normalize_path('dir1/')) 
     301        self.assertEqual('dir1', self.repos.normalize_path('/dir1/')) 
     302 
     303    def test_repos_normalize_rev(self): 
     304        self.assertEqual(6, self.repos.normalize_rev('latest')) 
     305        self.assertEqual(6, self.repos.normalize_rev('head')) 
     306        self.assertEqual(6, self.repos.normalize_rev('')) 
     307        self.assertEqual(6, self.repos.normalize_rev(None)) 
     308        self.assertEqual(5, self.repos.normalize_rev('5')) 
     309        self.assertEqual(5, self.repos.normalize_rev(5)) 
     310 
     311    def test_rev_navigation(self): 
     312        self.assertEqual(1, self.repos.oldest_rev) 
     313        self.assertEqual(None, self.repos.previous_rev(0)) 
     314        self.assertEqual(1, self.repos.previous_rev(2)) 
     315        self.assertEqual(6, self.repos.youngest_rev) 
     316        self.assertEqual(2, self.repos.next_rev(1)) 
     317        self.assertEqual(3, self.repos.next_rev(2)) 
     318        # ... 
     319        self.assertEqual(None, self.repos.next_rev(6)) 
     320 
     321    def test_has_node(self): 
     322        self.assertEqual(False, self.repos.has_node('/dir1', 3)) 
     323        self.assertEqual(True, self.repos.has_node('/dir1', 4)) 
     324 
     325    def test_get_node(self): 
     326        node = self.repos.get_node('/dir1') 
     327        self.assertEqual('dir1', node.name) 
     328        self.assertEqual('/dir1', node.path) 
     329        self.assertEqual(Node.DIRECTORY, node.kind) 
     330        self.assertEqual(5, node.rev) 
     331        self.assertEqual(1112372739, node.last_modified) 
     332        node = self.repos.get_node('/README.txt') 
     333        self.assertEqual('README.txt', node.name) 
     334        self.assertEqual('/README.txt', node.path) 
     335        self.assertEqual(Node.FILE, node.kind) 
     336        self.assertEqual(3, node.rev) 
     337        self.assertEqual(1112361898, node.last_modified) 
     338 
     339    def test_get_node_specific_rev(self): 
     340        node = self.repos.get_node('/dir1', 4) 
     341        self.assertEqual('dir1', node.name) 
     342        self.assertEqual('/dir1', node.path) 
     343        self.assertEqual(Node.DIRECTORY, node.kind) 
     344        self.assertEqual(4, node.rev) 
     345        self.assertEqual(1112370155, node.last_modified) 
     346        node = self.repos.get_node('/README.txt', 2) 
     347        self.assertEqual('README.txt', node.name) 
     348        self.assertEqual('/README.txt', node.path) 
     349        self.assertEqual(Node.FILE, node.kind) 
     350        self.assertEqual(2, node.rev) 
     351        self.assertEqual(1112361138, node.last_modified) 
     352 
     353    def test_get_dir_entries(self): 
     354        node = self.repos.get_node('/') 
     355        entries = node.get_entries() 
     356        self.assertEqual('README2.txt', entries.next().name) 
     357        self.assertEqual('dir1', entries.next().name) 
     358        self.assertEqual('README.txt', entries.next().name) 
     359        self.assertRaises(StopIteration, entries.next) 
     360 
     361    def test_get_file_entries(self): 
     362        node = self.repos.get_node('/README.txt') 
     363        entries = node.get_entries() 
     364        self.assertRaises(StopIteration, entries.next) 
     365 
     366    def test_get_dir_content(self): 
     367        node = self.repos.get_node('/dir1') 
     368        self.assertEqual(None, node.content_length) 
     369        self.assertEqual(None, node.content_type) 
     370        self.assertEqual(None, node.get_content()) 
     371 
     372    def test_get_file_content(self): 
     373        node = self.repos.get_node('/README.txt') 
     374        self.assertEqual(8, node.content_length) 
     375        self.assertEqual('text/plain', node.content_type) 
     376        self.assertEqual('A test.\n', node.get_content().read()) 
     377 
     378    def test_get_dir_properties(self): 
     379        f = self.repos.get_node('/dir1') 
     380        props = f.get_properties() 
     381        self.assertEqual(0, len(props)) 
     382 
     383    def test_get_file_properties(self): 
     384        f = self.repos.get_node('/README.txt') 
     385        props = f.get_properties() 
     386        self.assertEqual('native', props['svn:eol-style']) 
     387        self.assertEqual('text/plain', props['svn:mime-type']) 
     388 
     389    # Revision Log / node history  
     390 
     391    def test_get_node_history(self): 
     392        node = self.repos.get_node('/README2.txt') 
     393        history = node.get_history() 
     394        self.assertEqual(('README2.txt', 6, 'copy'), history.next()) 
     395        self.assertEqual(('README.txt', 3, 'edit'), history.next()) 
     396        self.assertEqual(('README.txt', 2, 'add'), history.next()) 
     397        self.assertRaises(StopIteration, history.next) 
     398 
     399    def test_get_node_history_follow_copy(self): 
     400        node = self.repos.get_node('dir1/dir3', ) 
     401        history = node.get_history() 
     402        self.assertEqual(('dir1/dir3', 5, 'copy'), history.next()) 
     403        self.assertEqual(('dir3', 4, 'add'), history.next()) 
     404        self.assertRaises(StopIteration, history.next) 
     405 
     406    # Revision Log / path history  
     407 
     408    def test_get_path_history(self): 
     409        history = self.repos.get_path_history('dir3', None) 
     410        self.assertEqual(('dir3', 5, 'delete'), history.next()) 
     411        self.assertEqual(('dir3', 4, 'add'), history.next()) 
     412        self.assertRaises(StopIteration, history.next) 
     413 
     414    def test_get_path_history_copied_file(self): 
     415        history = self.repos.get_path_history('README2.txt', None) 
     416        self.assertEqual(('README2.txt', 6, 'copy'), history.next()) 
     417        self.assertEqual(('README.txt', 3, 'unknown'), history.next()) 
     418        self.assertRaises(StopIteration, history.next) 
     419         
     420    def test_get_path_history_copied_dir(self): 
     421        history = self.repos.get_path_history('dir1/dir3', None) 
     422        self.assertEqual(('dir1/dir3', 5, 'copy'), history.next()) 
     423        self.assertEqual(('dir3', 4, 'unknown'), history.next()) 
     424        self.assertRaises(StopIteration, history.next) 
     425 
     426    def test_changeset_repos_creation(self): 
     427        chgset = self.repos.get_changeset(0) 
     428        self.assertEqual(0, chgset.rev) 
     429        self.assertEqual(None, chgset.message) 
     430        self.assertEqual(None, chgset.author) 
     431        self.assertEqual(1112349461, chgset.date) 
     432        self.assertRaises(StopIteration, chgset.get_changes().next) 
     433 
     434    def test_changeset_added_dirs(self): 
     435        chgset = self.repos.get_changeset(4) 
     436        self.assertEqual(4, chgset.rev) 
     437        self.assertEqual('More directories.', chgset.message) 
     438        self.assertEqual('john', chgset.author) 
     439        self.assertEqual(1112370155, chgset.date) 
     440 
     441        changes = chgset.get_changes() 
     442        self.assertEqual(('dir1', Node.DIRECTORY, 'add', None, -1), 
     443                         changes.next()) 
     444        self.assertEqual(('dir2', Node.DIRECTORY, 'add', None, -1), 
     445                         changes.next()) 
     446        self.assertEqual(('dir3', Node.DIRECTORY, 'add', None, -1), 
     447                         changes.next()) 
     448        self.assertRaises(StopIteration, changes.next) 
     449 
     450    def test_changeset_file_edit(self): 
     451        chgset = self.repos.get_changeset(3) 
     452        self.assertEqual(3, chgset.rev) 
     453        self.assertEqual('Fixed README.\n', chgset.message) 
     454        self.assertEqual('kate', chgset.author) 
     455        self.assertEqual(1112361898, chgset.date) 
     456 
     457        changes = chgset.get_changes() 
     458        self.assertEqual(('README.txt', Node.FILE, Changeset.EDIT, 
     459                          'README.txt', 2), changes.next()) 
     460        self.assertRaises(StopIteration, changes.next) 
     461 
     462    def test_changeset_dir_moves(self): 
     463        chgset = self.repos.get_changeset(5) 
     464        self.assertEqual(5, chgset.rev) 
     465        self.assertEqual('Moved directories.', chgset.message) 
     466        self.assertEqual('kate', chgset.author) 
     467        self.assertEqual(1112372739, chgset.date) 
     468 
     469        changes = chgset.get_changes() 
     470        self.assertEqual(('dir1/dir2', Node.DIRECTORY, Changeset.MOVE, 
     471                          'dir2', 4), changes.next()) 
     472        self.assertEqual(('dir1/dir3', Node.DIRECTORY, Changeset.MOVE, 
     473                          'dir3', 4), changes.next()) 
     474        self.assertRaises(StopIteration, changes.next) 
     475 
     476    def test_changeset_file_copy(self): 
     477        chgset = self.repos.get_changeset(6) 
     478        self.assertEqual(6, chgset.rev) 
     479        self.assertEqual('More things to read', chgset.message) 
     480        self.assertEqual('john', chgset.author) 
     481        self.assertEqual(1112381806, chgset.date) 
     482 
     483        changes = chgset.get_changes() 
     484        self.assertEqual(('README2.txt', Node.FILE, Changeset.COPY, 
     485                          'README.txt', 3), changes.next()) 
     486        self.assertRaises(StopIteration, changes.next) 
     487 
     488 
    281489def suite(): 
    282     return unittest.makeSuite(SubversionRepositoryTestCase, 'test', 
    283                               suiteClass=SubversionRepositoryTestSetup) 
     490    suite = unittest.TestSuite() 
     491    suite.addTest(unittest.makeSuite(SubversionRepositoryTestCase, 'test', 
     492                                     suiteClass=SubversionRepositoryTestSetup)) 
     493    suite.addTest(unittest.makeSuite(ScopedSubversionRepositoryTestCase, 'test', 
     494                                     suiteClass=SubversionRepositoryTestSetup)) 
     495    return suite 
    284496 
    285497if __name__ == '__main__': 
    286498    runner = unittest.TextTestRunner() 
  • trac/versioncontrol/svn_fs.py

     
    5454        yield item 
    5555 
    5656 
     57def _normalize_path(path): 
     58    """Remove leading "/", except for the root""" 
     59    return path and path.strip('/') or '/' 
     60 
     61def _scoped_path(scope, fullpath): 
     62    """Remove the leading scope from repository paths""" 
     63    if fullpath: 
     64        if scope == '/': 
     65            return _normalize_path(fullpath) 
     66        elif fullpath.startswith(scope.rstrip('/')): 
     67            return fullpath[len(scope):] or '/' 
     68 
     69 
    5770def _mark_weakpool_invalid(weakpool): 
    5871    if weakpool(): 
    5972        weakpool()._mark_invalid() 
     
    207220    def __del__(self): 
    208221        self.close() 
    209222 
     223    def has_node(self, path, rev, pool=None): 
     224        if not pool: 
     225            pool = self.pool 
     226        rev_root = fs.revision_root(self.fs_ptr, rev, pool()) 
     227        node_type = fs.check_path(rev_root, self.scope + path, pool()) 
     228        return node_type in _kindmap 
     229 
    210230    def normalize_path(self, path): 
    211         return (not path or path == '/') and '/' or path.strip('/') 
     231        return _normalize_path(path) 
    212232 
    213233    def normalize_rev(self, rev): 
    214234        try: 
     
    298318        subpool = Pool(self.pool) 
    299319        while rev: 
    300320            subpool.clear() 
    301             rev_root = fs.revision_root(self.fs_ptr, rev, subpool()) 
    302             node_type = fs.check_path(rev_root, path, subpool()) 
    303             if node_type in _kindmap: # then path exists at that rev 
     321            if self.has_node(path, rev, subpool): 
    304322                if expect_deletion: 
    305323                    # it was missing, now it's there again: 
    306324                    #  rev+1 must be a delete 
    307325                    yield path, rev+1, Changeset.DELETE 
    308326                newer = None # 'newer' is the previously seen history tuple 
    309327                older = None # 'older' is the currently examined history tuple 
    310                 for p, r in _get_history(path, self.authz, self.fs_ptr, 
    311                                          subpool, 0, rev, limit): 
    312                     older = (self.normalize_path(p), r, Changeset.ADD) 
     328                for p, r in _get_history(self.scope + path, self.authz, 
     329                                         self.fs_ptr, subpool, 0, rev, limit): 
     330                    older = (_scoped_path(self.scope, p), r, Changeset.ADD) 
    313331                    rev = self.previous_rev(r) 
    314332                    if newer: 
    315333                        if older[0] == path: 
     
    382400        pool = Pool(self.pool) 
    383401        for path, rev in _get_history(self.scoped_path, self.authz, self.fs_ptr, 
    384402                                      pool, 0, self._requested_rev, limit): 
    385             if rev > 0 and path.startswith(self.scope): 
    386                 older = (path[len(self.scope):], rev, Changeset.ADD) 
     403            scoped_path = _scoped_path(self.scope, path) 
     404            if rev > 0 and scoped_path: 
     405                older = (scoped_path, rev, Changeset.ADD) 
    387406                if newer: 
    388407                    change = newer[0] == older[0] and Changeset.EDIT or \ 
    389408                             Changeset.COPY 
     
    448467                continue 
    449468            if not path.startswith(self.scope[1:]): 
    450469                continue 
    451             base_path = None 
    452             if change.base_path: 
    453                 if change.base_path.startswith(self.scope): 
    454                     base_path = change.base_path[len(self.scope):] 
    455                 else: 
    456                     base_path = None 
     470            base_path = _scoped_path(self.scope, change.base_path) 
    457471            action = '' 
    458472            if not change.path: 
    459473                action = Changeset.DELETE 
  • trac/versioncontrol/cache.py

     
    8484    def get_node(self, path, rev=None): 
    8585        return self.repos.get_node(path, rev) 
    8686 
     87    def has_node(self, path, rev): 
     88        return self.repos.has_node(path, rev) 
     89 
    8790    def get_oldest_rev(self): 
    8891        return self.repos.oldest_rev 
    8992 
  • trac/versioncontrol/web_ui/log.py

     
    5454        import re 
    5555        match = re.match(r'/log(?:(/.*)|$)', req.path_info) 
    5656        if match: 
    57             req.args['path'] = match.group(1) 
     57            req.args['path'] = match.group(1) or '/' 
    5858            return 1 
    5959 
    6060    def process_request(self, req):