Edgewall Software

Ticket #8417: backend.py.3.patch

File backend.py.3.patch, 11.4 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                        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]