Edgewall Software
Modify

Opened 5 years ago

Closed 5 years ago

Last modified 5 years ago

#11610 closed enhancement (fixed)

Move Changeset, Node and Repository classes to model.py

Reported by: Ryan J Ollos Owned by: Ryan J Ollos
Priority: normal Milestone: 1.1.2
Component: version control Version:
Severity: normal Keywords:
Cc: Jun Omae Branch:
Release Notes:

Git and Mercurial raise a TracError rather than showing a traceback when attempting to view added and deleted files (#9775).

API Changes:
  • Abstract base classes have abc.ABCMeta metaclass and use abstractmethod decorator on abstract methods.
  • Mock has support for abstract base classes defined using abc.ABCMeta.
  • A CachedChangeset instance rather than an instance of the abstract Changeset class is passed to the repository change listeners.
  • Added EmptyChangeset class, an instance of which is returned by get_changes (in trac.versioncontrol.web_ui.util) when a NoSuchChangeset error is raised by the repository.

Description

As discussed in #11609, move theChangeset, Node and Repository classes to a new model.py module in trac/versioncontrol.

Attachments (2)

ReposBrowser1.png (22.2 KB ) - added by Ryan J Ollos 5 years ago.
ReposBrowser2.png (19.0 KB ) - added by Ryan J Ollos 5 years ago.

Download all attachments as: .zip

Change History (20)

comment:1 by Ryan J Ollos, 5 years ago

Status: newassigned

comment:2 by Ryan J Ollos, 5 years ago

Looking it at it more closely now, it could be that the reason the classes are in api.py rather than model.py is that they are abstract classes.

comment:3 by Ryan J Ollos, 5 years ago

Some additional experimental changes in log:rjollos.git:t11610

The classes in the abc module were utilized in #11581. There are other classes in the repository which are made abstract by raising NotImplementedErrors in methods, but the ABCMeta class forces subclasses to adhere to the contract (comment:2:ticket:11581), and more clearly identifies the base classes as abstract.

Some unit tests are currently failing due to:

  1. GitRepository doesn't implemented get_path_history (#9775):
    ======================================================================
    ERROR: test_node_get_history_with_empty_commit (tracopt.versioncontrol.git.tests.PyGIT.NormalTestCase)
    ----------------------------------------------------------------------
    Traceback (most recent call last):
      File "/home/user/Workspace/tracdev/teo-rjollos.git/tracopt/versioncontrol/git/tests/PyGIT.py", line 251, in test_node_get_history_with_empty_commit
        repos = self.env.get_repository('gitrepos')
      File "/home/user/Workspace/tracdev/teo-rjollos.git/trac/env.py", line 567, in get_repository
        return RepositoryManager(self).get_repository(reponame)
      File "/home/user/Workspace/tracdev/teo-rjollos.git/trac/versioncontrol/api.py", line 575, in get_repository
        repoinfo.copy())
      File "/home/user/Workspace/tracdev/teo-rjollos.git/tracopt/versioncontrol/git/git_fs.py", line 280, in get_repository
        use_committer_time=self.use_committer_time,
    TypeError: Can't instantiate abstract class GitRepository with abstract methods get_path_history
    
  1. A Mock object can't be created directly from a base class with abstractmethods. It is possible that we need to modify Mock in order to deal with abstract base classes. I'm not sure yet what is the best approach to take on this.
    ======================================================================
    ERROR: test_clean_sync (trac.versioncontrol.tests.cache.CacheTestCase)
    ----------------------------------------------------------------------
    Traceback (most recent call last):
      File "/home/user/Workspace/tracdev/teo-rjollos.git/trac/versioncontrol/tests/cache.py", line 158, in test_clean_sync
        youngest_rev=2)
      File "/home/user/Workspace/tracdev/teo-rjollos.git/trac/versioncontrol/tests/cache.py", line 54, in get_repos
        next_rev=(lambda x: int(x) < youngest_rev and x + 1 \
      File "/home/user/Workspace/tracdev/teo-rjollos.git/trac/test.py", line 94, in Mock
        mock = cls(*initargs)
    TypeError: Can't instantiate abstract class Mock with abstract methods close, get_changes, get_changeset, get_node, get_oldest_rev, get_path_history, get_youngest_rev, next_rev, normalize_path, normalize_rev, previous_rev, rev_older_than
    

comment:4 by Ryan J Ollos, 5 years ago

Changeset is instantiated directly in CachedRepository. I fixed the failing test by instead instantiating the derived class. The only place I see the object used is: tags/trac-1.0.1/trac/versioncontrol/api.py@:662,674#L657. So now a GitChangeset, SubversionChangeset or other derived type is passed to the change listener. Are there any possible negative consequences to this that I'm not seeing?

As for creating Mock instances of ABCs, it seemed most straightforward to just remove the abstract methods from the class. Like any other methods in a mocked object, an AttributeError will be raised if methods omitted from the Mock are utilized in a function under test.

Latest changes added to log:rjollos.git:t11610.

in reply to:  4 comment:5 by Jun Omae, 5 years ago

Cc: Jun Omae added

Changeset is instantiated directly in CachedRepository. I fixed the failing test by instead instantiating the derived class. The only place I see the object used is: tags/trac-1.0.1/trac/versioncontrol/api.py@:662,674#L657. So now a GitChangeset, SubversionChangeset or other derived type is passed to the change listener. Are there any possible negative consequences to this that I'm not seeing?

I think it would be good to create CachedChangeset instance rather than Changeset. Also, sync_changeset method is for only cached repository. Then, GitChangeset and SubversionChangeset never be passed to the change listener.

See [058936ed/jomae.git].

As for creating Mock instances of ABCs, it seemed most straightforward to just remove the abstract methods from the class. Like any other methods in a mocked object, an AttributeError will be raised if methods omitted from the Mock are utilized in a function under test.

We could add support for ABCMeta class as base class of Mock. See [8f55ca19/jomae.git].

comment:6 by Ryan J Ollos, 5 years ago

API Changes: modified (diff)
Release Notes: modified (diff)

Those changes look good, thanks. Committed to trunk in [12782:12783].

We'll need to implement get_path_history in the MercurialPlugin's Repository subclass. I don't have the necessary permission to push to the branch.

  • tracext/hg/backend.py

    # HG changeset patch
    # User ryan.j.ollos
    # Date 1400115750 25200
    # Branch 1.0
    # Node ID 251cde2ad7fdddbfbde7542bccd2a2f4a129b9c9
    # Parent  21bb3cc19977ee1f5e546fc21f440ebfd6231404
    Implemented abstract method `get_path_history`, which currently displays just a `TracError`. Refs #11610, #9775.
    
    diff -r 21bb3cc19977 -r 251cde2ad7fd tracext/hg/backend.py
    a b  
    3333from trac.util import arity
    3434from trac.util.datefmt import FixedOffset, utc
    3535from trac.util.text import exception_to_unicode, shorten_line, to_unicode
    36 from trac.util.translation import domain_functions
     36from trac.util.translation import _, domain_functions
    3737from trac.versioncontrol.api import Changeset, Node, Repository, \
    3838                                    IRepositoryConnector, RepositoryManager, \
    3939                                    NoSuchChangeset, NoSuchNode
     
    747747    def get_youngest_rev(self):
    748748        return self.changectx().hex()
    749749
     750    def get_path_history(self, path, rev=None, limit=None):
     751        raise TracError(_("Unsupported \"Show only adds and deletes\""))
     752
    750753    def previous_rev(self, rev, path=''): # FIXME: path ignored for now
    751754        for p in self.changectx(rev).parents():
    752755            if p:
Last edited 5 years ago by Ryan J Ollos (previous) (diff)

comment:7 by Christian Boos, 5 years ago

Change looks fine, you can push it (I've added the perm).

However, the failure of one plugin shouldn't bring the whole BrowserModule down (as can be seen on the demo-1.1 instance today):

2014-05-21 10:16:37,089 Trac[chrome] ERROR: Error with navigation contributor BrowserModule: TypeError: Can't instantiate abstract class MercurialRepository with abstract methods get_path_history

by Ryan J Ollos, 5 years ago

Attachment: ReposBrowser1.png added

by Ryan J Ollos, 5 years ago

Attachment: ReposBrowser2.png added

in reply to:  7 comment:8 by Ryan J Ollos, 5 years ago

Replying to cboos:

However, the failure of one plugin shouldn't bring the whole BrowserModule down (as can be seen on the demo-1.1 instance today):

We could trap the TypeError in RepositoryManager.get_repository:

  • trac/versioncontrol/api.py

    diff --git a/trac/versioncontrol/api.py b/trac/versioncontrol/api.py
    index 9a26e34..76baef1 100644
    a b class RepositoryManager(Component):  
    571571                    if not os.path.isabs(rdir):
    572572                        rdir = os.path.join(self.env.path, rdir)
    573573                    connector = self._get_connector(rtype)
    574                     repos = connector.get_repository(rtype, rdir,
    575                                                      repoinfo.copy())
    576                     repositories[reponame] = repos
     574                    try:
     575                        repos = connector.get_repository(rtype, rdir,
     576                                                         repoinfo.copy())
     577                    except TypeError as e:  # Incomplete implementation of
     578                        self.log.error(e)   # abstract class
     579                        repos = None
     580                    else:
     581                        repositories[reponame] = repos
    577582                return repos
    578583
    579584    def get_repository_by_path(self, path):

In the repository browser we'd see:

In the log we'd see:

02:15:45 AM Trac[api] ERROR: Can't instantiate abstract class MercurialRepository with abstract methods get_path_history

comment:9 by Jun Omae, 5 years ago

Should we revert the change for Repository.get_path_history to keep backward-compatibilities?

  • trac/versioncontrol/api.py

    diff --git a/trac/versioncontrol/api.py b/trac/versioncontrol/api.py
    index 9a26e34..61bc40f 100644
    a b class Repository(object):  
    944944        """
    945945        pass
    946946
    947     @abstractmethod
    948947    def get_path_history(self, path, rev=None, limit=None):
    949948        """Retrieve all the revisions containing this path.
    950949
    class Repository(object):  
    952951        ''newer'' than `rev` should be returned).
    953952        The result format should be the same as the one of Node.get_history()
    954953        """
    955         pass
     954        raise NotImplementedError
    956955
    957956    @abstractmethod
    958957    def normalize_path(self, path):

in reply to:  9 comment:10 by Ryan J Ollos, 5 years ago

Replying to jomae:

Should we revert the change for Repository.get_path_history to keep backward-compatibilities?

That would defeat the purpose of the abstract interface. MercurialPlugin is wrong for not implementing that method, and now we are enforcing it in on the trunk, forcing MercurialPlugin to properly implement the interface of the Repository class. We are not changing the interface, we are just enforcing it. MercurialPlugin can implement the method and be compatible with all versions of Trac, thus we aren't putting any burden on the maintainer of the MercurialPlugin. I don't think it is burdensome to force the upgrade of plugins when moving to a new major Trac version.

in reply to:  7 comment:11 by Ryan J Ollos, 5 years ago

Replying to cboos:

However, the failure of one plugin shouldn't bring the whole BrowserModule down (as can be seen on the demo-1.1 instance today):

I'm considering whether the patch in comment:8 would be sufficient if we trap Exception rather than TypeError. The issue will be explored in #11631.

comment:12 by Ryan J Ollos, 5 years ago

Resolution: fixed
Status: assignedclosed

Change to MercurialPlugin committed in [96c854b89f5c/mercurial-plugin]. The Repository.get_path_history method won't be made abstract until Trac 1.3.1, in order to give time for Trac instances to update VC plugins that may not implement this method in earlier versions (such as MercurialPlugin): [12832].

comment:13 by Ryan J Ollos, 5 years ago

I encountered the error when navigating to a repository: TypeError: Can't instantiate abstract class Changeset with abstract methods get_changes:

File "/home/user/Workspace/tracdev/teo-rjollos.git/trac/web/main.py", line 530, in _dispatch_request
  dispatcher.dispatch(req)
File "/home/user/Workspace/tracdev/teo-rjollos.git/trac/web/main.py", line 223, in dispatch
  resp = chosen_handler.process_request(req)
File "/home/user/Workspace/tracdev/teo-rjollos.git/trac/versioncontrol/web_ui/browser.py", line 406, in process_request
  dir_data = self._render_dir(req, repos, node, rev, order, desc)
File "/home/user/Workspace/tracdev/teo-rjollos.git/trac/versioncontrol/web_ui/browser.py", line 581, in _render_dir
  self.log)
File "/home/user/Workspace/tracdev/teo-rjollos.git/trac/versioncontrol/web_ui/util.py", line 45, in get_changes
  datetime(1970, 1, 1, tzinfo=utc))

Either Changeset will need to be non-abstract, or some other solution will need to be found for the usage in trunk/trac/versioncontrol/web_ui/util.py@12888:44-45#L36.

comment:14 by Jun Omae, 5 years ago

That is introduced in [10116], see comment:4:ticket:7800.

How about adding pseudo class for dummy changesets like this?

class PseudoChangeset(Changeset):  # or named `NullChangeset`
    def get_changes(self):
        return iter([])

in reply to:  14 ; comment:15 by Ryan J Ollos, 5 years ago

Replying to jomae:

How about adding pseudo class for dummy changesets like this?

That sounds good. How does EmptyChangeset sound? NullChangeset seems okay as well.

in reply to:  15 comment:16 by Jun Omae, 5 years ago

Replying to rjollos:

Replying to jomae:

How about adding pseudo class for dummy changesets like this?

That sounds good. How does EmptyChangeset sound? NullChangeset seems okay as well.

EmptyChangeset sounds good and clear!

comment:17 by Ryan J Ollos, 5 years ago

Proposed changes in log:rjollos.git:t11610.4.

comment:18 by Ryan J Ollos, 5 years ago

API Changes: modified (diff)

Change discussed in comment:13 - comment:17 committed to trunk in [12983:12984].

Last edited 5 years ago by Ryan J Ollos (previous) (diff)

Modify Ticket

Change Properties
Set your email in Preferences
Action
as closed The owner will remain Ryan J Ollos.
The resolution will be deleted.
to The owner will be changed from Ryan J Ollos to the specified user.

Add Comment


E-mail address and name can be saved in the Preferences .
 
Note: See TracTickets for help on using tickets.