Edgewall Software

Ticket #8417: backend.py.2.patch

File backend.py.2.patch, 10.9 KB (added by me@…, 12 years 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                        print 'inserting', (self.id, srev, path, kind, action, bpath,
     566                                  brev)
     567                        print e
     568                if feedback:
     569                    feedback(self.rev_db(srev))
     570
     571                return (self.id, srev, to_utimestamp(cset.date), cset.author, cset.message)
     572
     573            # sql_revs: all nodes already synced in revision the table
     574            sql_string = """
     575                         SELECT rev FROM revision
     576                         WHERE repos = %s
     577                         """
     578            cursor.execute(sql_string, (self.id, ))
     579            sql_revs = set(int(row[0]) for row in cursor.fetchall())
     580           
     581            # add_revs: new revisions to be synced
     582            # del_revs: There might be revisions synced that are outdated.
     583            add_revs = [ _cache_and_get_revision_info(srev) for srev in repo_revs - sql_revs ]
     584            del_revs = [ (self.id, srev) for srev in sql_revs - repo_revs ]
     585
     586            sql_string = """
     587                         INSERT INTO revision (repos, rev, time, author, message)
     588                         VALUES (%s, %s, %s, %s, %s)
     589                         """
     590            # We insert the new revisions' information into Trac's revision table
     591            # trac.db.utils.executemany can not be passed an iterator
     592            # Constructing a list here slow things down, but it is the only way at the moment
     593            cursor.executemany(sql_string, list(add_revs))
     594
     595            sql_string = """
     596                         DELETE FROM revision
     597                         WHERE repos = %s AND rev = %s
     598                         """
     599            cursor.executemany(sql_string, list(del_revs))
     600
     601            # Update the most recent revision for the browser.
     602            cursor.execute("""
     603                UPDATE repository SET value=%s WHERE id=%s AND name=%s
     604                """, (str(self.repos.changectx().hex()), self.id, CACHE_YOUNGEST_REV))
     605            del self.metadata
     606
     607
     608class MercurialCachedChangeset(CachedChangeset):
     609    pass
    441610
    442611### Version Control API
    443612   
     
    560729        try:
    561730            return repo[repo.lookup(self.to_s(rev))].rev()
    562731        except (HgLookupError, RepoError):
     732            import pdb; pdb.set_trace()
    563733            raise NoSuchChangeset(rev)
    564734
    565735    def display_rev(self, rev):
     
    683853                             self.changectx(rev))
    684854
    685855    def get_oldest_rev(self):
    686         return 0
     856        return nullrev
    687857
    688858    def get_youngest_rev(self):
    689859        return self.changectx().hex()
    690860   
    691861    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
     862        for parent_rev in self.changectx(rev).ancestors():
     863            return parent_rev.hex()
     864        return None
    695865   
    696866    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
     867        for following_rev in self.changectx(rev).descendants():
     868            return following_rev.hex()
     869        return None
    710870   
    711871    def rev_older_than(self, rev1, rev2):
    712872        # FIXME use == and ancestors?
     
    779939            if old_node.manifest[old_node.str_path] != \
    780940                   new_node.manifest[new_node.str_path]:
    781941                yield(old_node, new_node, Node.FILE, Changeset.EDIT)
    782            
     942
    783943
    784944class MercurialNode(Node):
    785945    """A path in the repository, at a given revision.
     
    11891349            # TODO: find a way to detect conflicts and show how they were
    11901350            #       solved (kind of 3-way diff - theirs/mine/merged)
    11911351            edits = [p for p in parents if str_file in p.manifest()]
     1352            edits = edits[:1]
    11921353
    11931354            if str_file not in manifest:
    11941355                str_deletions[str_file] = edits[0]