Edgewall Software

Ticket #8417: backend.py.3.patch

File backend.py.3.patch, 11.4 KB (added by me@…, 13 months ago)

Diff for the revised patch

  • .py

    old new  
    3131from trac.versioncontrol.api import Changeset, Node, Repository, \ 
    3232                                    IRepositoryConnector, RepositoryManager, \ 
    3333                                    NoSuchChangeset, NoSuchNode 
     34from trac.versioncontrol.cache import CachedRepository, CachedChangeset, \ 
     35                                      CACHE_METADATA_KEYS, CACHE_REPOSITORY_DIR, CACHE_YOUNGEST_REV, \ 
     36                                      _kindmap, _actionmap 
    3437from trac.versioncontrol.web_ui import IPropertyRenderer, RenderedProperty 
     38from trac.util.datefmt import from_utimestamp, to_utimestamp 
    3539from trac.wiki import IWikiSyntaxProvider 
    3640 
    3741# -- plugin i18n 
     
    381385            self._setup_ui(self.hgrc) 
    382386        repos = MercurialRepository(dir, params, self.log, self) 
    383387        repos.version_info = self._version_info 
    384         return repos 
     388        cached_repos = MercurialCachedRepository(self.env, repos, self.log) 
     389        cached_repos.has_linear_changesets = False 
     390        return cached_repos 
    385391 
    386392    # IWikiSyntaxProvider methods 
    387393     
     
    438444                     title=errmsg, rel="nofollow") 
    439445 
    440446 
     447class MercurialCachedRepository(CachedRepository): 
     448 
     449    def display_rev(self, rev): 
     450        return self.repos.display_rev(rev) 
     451 
     452    def normalize_rev(self, rev): 
     453        if rev is None or isinstance(rev, basestring) and \ 
     454               rev.lower() in ('', 'head', 'latest', 'youngest'): 
     455            return self.rev_db(self.youngest_rev or nullrev) 
     456        else: 
     457            return self.repos.normalize_rev(rev) 
     458 
     459    def db_rev(self, rev): 
     460        return self.repos.short_rev(rev) 
     461 
     462    def rev_db(self, rev): 
     463        return self.repos.normalize_rev(rev) 
     464 
     465    def get_changeset(self, rev): 
     466        return MercurialCachedChangeset(self, self.normalize_rev(rev), self.env) 
     467 
     468    def sync(self, feedback=None, clean=False): 
     469        if clean: 
     470            self.log.info('Cleaning cache') 
     471            @self.env.with_transaction() 
     472            def do_clean(db): 
     473                cursor = db.cursor() 
     474                cursor.execute("DELETE FROM revision WHERE repos=%s", 
     475                               (self.id,)) 
     476                cursor.execute("DELETE FROM node_change WHERE repos=%s", 
     477                               (self.id,)) 
     478                cursor.executemany(""" 
     479                    DELETE FROM repository WHERE id=%s AND name=%s 
     480                    """, [(self.id, k) for k in CACHE_METADATA_KEYS]) 
     481                cursor.executemany(""" 
     482                    INSERT INTO repository (id,name,value) VALUES (%s,%s,%s) 
     483                    """, [(self.id, k, '') for k in CACHE_METADATA_KEYS]) 
     484                del self.metadata 
     485 
     486        metadata = self.metadata 
     487         
     488        @self.env.with_transaction() 
     489        def do_transaction(db): 
     490            cursor = db.cursor() 
     491            invalidate = False 
     492     
     493            # -- check that we're populating the cache for the correct 
     494            #    repository 
     495            repository_dir = metadata.get(CACHE_REPOSITORY_DIR) 
     496            if repository_dir: 
     497                # directory part of the repo name can vary on case insensitive 
     498                # fs 
     499                if os.path.normcase(repository_dir) \ 
     500                        != os.path.normcase(self.name): 
     501                    self.log.info("'repository_dir' has changed from %r to %r", 
     502                                  repository_dir, self.name) 
     503                    raise TracError(_("The repository directory has changed, " 
     504                                      "you should resynchronize the " 
     505                                      "repository with: trac-admin $ENV " 
     506                                      "repository resync '%(reponame)s'", 
     507                                      reponame=self.reponame or '(default)')) 
     508            elif repository_dir is None: #  
     509                self.log.info('Storing initial "repository_dir": %s', 
     510                              self.name) 
     511                cursor.execute(""" 
     512                    INSERT INTO repository (id,name,value) VALUES (%s,%s,%s) 
     513                    """, (self.id, CACHE_REPOSITORY_DIR, self.name)) 
     514                invalidate = True 
     515            else: # 'repository_dir' cleared by a resync 
     516                self.log.info('Resetting "repository_dir": %s', self.name) 
     517                cursor.execute(""" 
     518                    UPDATE repository SET value=%s WHERE id=%s AND name=%s 
     519                    """, (self.name, self.id, CACHE_REPOSITORY_DIR)) 
     520                invalidate = True 
     521     
     522            # -- insert a 'youngeset_rev' for the repository if necessary 
     523            if metadata.get(CACHE_YOUNGEST_REV) is None: 
     524                cursor.execute(""" 
     525                    INSERT INTO repository (id,name,value) VALUES (%s,%s,%s) 
     526                    """, (self.id, CACHE_YOUNGEST_REV, '')) 
     527                invalidate = True 
     528     
     529            if invalidate: 
     530                del self.metadata 
     531 
     532        # -- retrieve the youngest revision in the repository and the youngest 
     533        #    revision cached so far 
     534        self.repos.clear() 
     535 
     536        kindmap = dict(zip(_kindmap.values(), _kindmap.keys())) 
     537        actionmap = dict(zip(_actionmap.values(), _actionmap.keys())) 
     538 
     539        # Retrieve all revisions. 
     540        repo_revs = set(self.db_rev(rev) for rev in self.repos.repo.changelog) 
     541         
     542        @self.env.with_transaction() 
     543        def do_transaction(db): 
     544            cursor = db.cursor() 
     545 
     546            # Note: The idea using sets for add_revs/del_revs is borrowed from 
     547            # https://github.com/maraujop/TracMercurialChangesetPlugin/blob/master/mercurialchangeset/admin.py 
     548            # The term "nodes" are change to "revs" for consistency of terminology. 
     549            def _cache_and_get_revision_info(srev): 
     550                cset = self.repos.get_changeset(self.rev_db(srev)) 
     551 
     552                # This is the time-consuming part, so we feedback here. 
     553                for path, kind, action, bpath, brev in cset.get_changes(): 
     554                    kind = kindmap[kind] 
     555                    action = actionmap[action] 
     556                    try: 
     557                        cursor.execute(""" 
     558                            INSERT INTO node_change 
     559                                (repos,rev,path,node_type, 
     560                                 change_type,base_path,base_rev) 
     561                            VALUES (%s,%s,%s,%s,%s,%s,%s) 
     562                            """, (self.id, srev, path, kind, action, bpath, 
     563                                  brev)) 
     564                    except Exception, e: 
     565                        self.log.error('Error %s while inserting into node_change: %s', 
     566                                       e, (self.id, srev, path, kind, action, bpath, brev)) 
     567                if feedback: 
     568                    feedback(self.rev_db(srev)) 
     569 
     570                return (self.id, srev, to_utimestamp(cset.date), cset.author, cset.message) 
     571 
     572            # sql_revs: all nodes already synced in revision the table 
     573            sql_string = """ 
     574                         SELECT rev FROM revision 
     575                         WHERE repos = %s 
     576                         """ 
     577            cursor.execute(sql_string, (self.id, )) 
     578            sql_revs = set(int(row[0]) for row in cursor.fetchall()) 
     579             
     580            # add_revs: new revisions to be synced 
     581            # del_revs: There might be revisions synced that are outdated. 
     582            add_revs = [ _cache_and_get_revision_info(srev) for srev in repo_revs - sql_revs ] 
     583            del_revs = [ (self.id, srev) for srev in sql_revs - repo_revs ] 
     584 
     585            sql_string = """ 
     586                         INSERT INTO revision (repos, rev, time, author, message) 
     587                         VALUES (%s, %s, %s, %s, %s) 
     588                         """ 
     589            # We insert the new revisions' information into Trac's revision table 
     590            # trac.db.utils.executemany can not be passed an iterator 
     591            # Constructing a list here slow things down, but it is the only way at the moment 
     592            cursor.executemany(sql_string, list(add_revs)) 
     593 
     594            sql_string = """ 
     595                         DELETE FROM revision 
     596                         WHERE repos = %s AND rev = %s 
     597                         """ 
     598            cursor.executemany(sql_string, list(del_revs)) 
     599 
     600            # Update the most recent revision for the browser. 
     601            cursor.execute(""" 
     602                UPDATE repository SET value=%s WHERE id=%s AND name=%s 
     603                """, (str(self.repos.changectx().hex()), self.id, CACHE_YOUNGEST_REV)) 
     604            del self.metadata 
     605 
     606 
     607class MercurialCachedChangeset(CachedChangeset): 
     608 
     609    hg_properties = [ 
     610        N_("Parents:"), N_("Children:"), N_("Branch:"), N_("Tags:") 
     611    ] 
     612 
     613    def __init__(self, repos, rev, env): 
     614        super(MercurialCachedChangeset, self).__init__(repos, rev, env) 
     615        self.ctx = repos.repos.changectx(rev) 
     616        self.branch = repos.repos.to_u(self.ctx.branch()) 
     617 
     618    def get_branches(self): 
     619        """Yield branch names to which this changeset belong.""" 
     620        return self.branch and [(self.branch,  
     621                                len(self.ctx.children()) == 0)] or [] 
    441622 
    442623### Version Control API 
    443624     
     
    560741        try: 
    561742            return repo[repo.lookup(self.to_s(rev))].rev() 
    562743        except (HgLookupError, RepoError): 
     744            import pdb; pdb.set_trace() 
    563745            raise NoSuchChangeset(rev) 
    564746 
    565747    def display_rev(self, rev): 
     
    683865                             self.changectx(rev)) 
    684866 
    685867    def get_oldest_rev(self): 
    686         return 0 
     868        return nullrev 
    687869 
    688870    def get_youngest_rev(self): 
    689871        return self.changectx().hex() 
    690872     
    691873    def previous_rev(self, rev, path=''): # FIXME: path ignored for now 
    692         for p in self.changectx(rev).parents(): 
    693             if p: 
    694                 return p.hex() # always follow first parent 
     874        for parent_rev in self.changectx(rev).ancestors(): 
     875            return parent_rev.hex() 
     876        return None 
    695877     
    696878    def next_rev(self, rev, path=''): 
    697         ctx = self.changectx(rev) 
    698         if path: # might be a file 
    699             fc = filectx(self.repo, self.to_s(path), ctx.node()) 
    700             # Note: the simpler form below raises an HgLookupError for a dir 
    701             # fc = ctx.filectx(self.to_s(path)) 
    702             if fc: # it is a file 
    703                 for c in fc.children(): 
    704                     return c.hex() 
    705                 else: 
    706                     return None 
    707         # it might be a directory (not supported for now) FIXME 
    708         for c in ctx.children(): 
    709             return c.hex() # always follow first child 
     879        for following_rev in self.changectx(rev).descendants(): 
     880            return following_rev.hex() 
     881        return None 
    710882    
    711883    def rev_older_than(self, rev1, rev2): 
    712884        # FIXME use == and ancestors? 
     
    779951            if old_node.manifest[old_node.str_path] != \ 
    780952                   new_node.manifest[new_node.str_path]: 
    781953                yield(old_node, new_node, Node.FILE, Changeset.EDIT) 
    782              
     954 
    783955 
    784956class MercurialNode(Node): 
    785957    """A path in the repository, at a given revision. 
     
    11891361            # TODO: find a way to detect conflicts and show how they were  
    11901362            #       solved (kind of 3-way diff - theirs/mine/merged) 
    11911363            edits = [p for p in parents if str_file in p.manifest()] 
     1364            edits = edits[:1] 
    11921365 
    11931366            if str_file not in manifest: 
    11941367                str_deletions[str_file] = edits[0]