| | 447 | class 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 | |
| | 607 | class 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 [] |