[Checkins] SVN: z3reload/trunk/ Initial import of z3reload code.
Gintautas Miliauskas
gintas at pov.lt
Sun Aug 10 20:21:50 EDT 2008
Log message for revision 89606:
Initial import of z3reload code.
Changed:
A z3reload/trunk/
A z3reload/trunk/README.txt
A z3reload/trunk/__init__.py
A z3reload/trunk/configure.zcml
A z3reload/trunk/ftests/
A z3reload/trunk/ftests/__init__.py
A z3reload/trunk/ftests/dynamic.pt
A z3reload/trunk/ftests/dynamic.py
A z3reload/trunk/ftests/dynamic_orig.py
A z3reload/trunk/ftests/ftesting.zcml
A z3reload/trunk/ftests/reload.txt
A z3reload/trunk/ftests/tests.py
A z3reload/trunk/meta.zcml
A z3reload/trunk/metaconfigure.py
A z3reload/trunk/metadirectives.py
A z3reload/trunk/package-includes/
A z3reload/trunk/package-includes/z3reload-configure.zcml
A z3reload/trunk/package-includes/z3reload-ftesting.zcml
A z3reload/trunk/package-includes/z3reload-meta.zcml
A z3reload/trunk/reload.py
A z3reload/trunk/subscriber.py
-=-
Added: z3reload/trunk/README.txt
===================================================================
--- z3reload/trunk/README.txt (rev 0)
+++ z3reload/trunk/README.txt 2008-08-11 00:21:49 UTC (rev 89606)
@@ -0,0 +1,123 @@
+z3reload
+========
+
+Version 0.1
+
+http://gintas.pov.lt/z3reload
+
+
+z3reload is a Zope 3 product that enables automatic reloading of view code.
+
+Make sure to read the *pitfalls* section before you delve in.
+
+View instances are short-lived and references to them are stored very
+infrequently, which makes them a good candidate for dynamic reloading.
+In addition they are frequently the largest and most complex part of
+the code in a typical web application. Even within the restrictions of
+this implementation automatic code reloading is very handy to have as Zope 3
+can take a while to restart.
+
+Code of z3reload now resides at the Zope 3 Base (http://codespeak.net/z3).
+Use the following command to grab the latest trunk using Subversion:
+
+ svn checkout http://codespeak.net/svn/z3/z3reload/trunk z3reload
+
+
+Installation
+------------
+
+Copy the z3reload directory where Zope 3 can find it, copy all files in
+the package-includes/ subdirectory to package-includes/ in Zope 3.
+
+
+Configuration
+-------------
+
+In package-includes/z3reload-configure.zcml (the global one which you created,
+not the one inside the package), the namespace `reload` should be registered,
+like this:
+
+<configure xmlns="http://namespaces.zope.org/zope"
+ xmlns:reload="http://namespaces.pov.lt/z3reload">
+
+In the same file you can specify individual classes, modules or packages which
+contain views that should be made reloadable using the ZCML directive
+`reload`. Here is an example:
+
+<reload:reload
+ classes="somepackage.somemodule.SomeView
+ somepackage.anothermodule.AnotherView"
+ modules="anotherpackage.views"
+ packages="thirdpackage.browser"
+ />
+
+This configuration would make views SomeView and AnotherView reloadable.
+All views directly in anotherpackage.views, for example,
+anotherpackage.views.FooView (but not anotherpackage.views.admin.BarView),
+would be included too. Finally, all views in all modules that reside
+in thirdpackage.browser would be processed (thirdpackage.browser.XView,
+thirdpackage.browser.admin.YView, etc.).
+
+
+Usage
+-----
+
+Use the views as you normally would. The view code will be automatically
+reloaded before just before rendering the view each time.
+
+
+Pitfalls
+--------
+
+Only the module that the view code resides in will be reloaded. E.g., if
+you have a view mypackage.browser.admin.AdminView that inherits from
+mypackage.browser.ViewBase, the mypackage.browser module will not be
+reloaded, and therefore changes in ViewBase will not take effect.
+
+It is important to understand the implications of reloading a Python
+module. Basically, all objects -- classes, functions and others --
+defined in the top level of the module "change". Old references
+(frequently in the form of imports) from other modules, however, will
+still point to the old objects. This way you can end up with two
+different references to distinct versions of the same class, which may
+cause unexpected behaviour with issubclass(). A similar problem can arise
+if the module defines interfaces which other modules use.
+
+z3reload can deal with updated views, but it will not notice changes in
+other components: adapters, utilities, subscribers. It will work with
+code outside the components (top-level functions and classes other than
+the registered one) though. This is because Zope 3 stores references to
+the components at startup time. It should be possible to reload these
+components automatically too, but that would probably require a different
+approach, because we would not be able not use the mixin hack.
+
+In general it is a good idea to only use automatic reloading for
+non-structural changes such as defining or modifying methods of views
+and do an old-fashioned server restart when you make a more significant
+change.
+
+
+Implementation details
+----------------------
+
+z3reload waits for the DatabaseOpened event, when all view registration has
+been completed, and then walks through the global adapter registry. For
+each view to be processed according to the `reload` directive it installs
+the mixin Reloader.
+
+Actually, Zope 3 ZCML directives that register views do not register the
+plain view class as a multi-adapter. Instead, they dynamically construct
+a new type which inherits from the given class and from a `simple view` class
+(that would be zope.app.pagetemplate.simpleviewclass.simple for views that
+have page templates defined in ZCML, and
+zope.app.publisher.browser.viewmeta.simple for views that don't). We use
+this fact and add Reloader as the first base class.
+
+Reloader overrides the __init__() method of the real view. In this method
+the module of the class is reloaded and the attribute __bases__ of the class
+is updated with a new reference to the reloaded class. Then __init__ of the
+actual reloaded view is invoked.
+
+
+Gintautas Miliauskas <gintas at pov.lt>
+2005-08-20
Property changes on: z3reload/trunk/README.txt
___________________________________________________________________
Name: svn:eol-style
+ native
Added: z3reload/trunk/__init__.py
===================================================================
--- z3reload/trunk/__init__.py (rev 0)
+++ z3reload/trunk/__init__.py 2008-08-11 00:21:49 UTC (rev 89606)
@@ -0,0 +1,2 @@
+# Set this to True to list processed view classes on stderr.
+BLATHER = False
Property changes on: z3reload/trunk/__init__.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: z3reload/trunk/configure.zcml
===================================================================
--- z3reload/trunk/configure.zcml (rev 0)
+++ z3reload/trunk/configure.zcml 2008-08-11 00:21:49 UTC (rev 89606)
@@ -0,0 +1,9 @@
+<?xml version="1.0"?>
+<configure xmlns="http://namespaces.zope.org/zope">
+
+ <subscriber
+ for="zope.app.appsetup.interfaces.IDatabaseOpenedEvent"
+ handler=".subscriber.database_opened"
+ />
+
+</configure>
Property changes on: z3reload/trunk/configure.zcml
___________________________________________________________________
Name: svn:eol-style
+ native
Added: z3reload/trunk/ftests/__init__.py
===================================================================
Property changes on: z3reload/trunk/ftests/__init__.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: z3reload/trunk/ftests/dynamic.pt
===================================================================
--- z3reload/trunk/ftests/dynamic.pt (rev 0)
+++ z3reload/trunk/ftests/dynamic.pt 2008-08-11 00:21:49 UTC (rev 89606)
@@ -0,0 +1 @@
+<p>The number is <span tal:replace="view/number" /></p>
Property changes on: z3reload/trunk/ftests/dynamic.pt
___________________________________________________________________
Name: svn:eol-style
+ native
Added: z3reload/trunk/ftests/dynamic.py
===================================================================
--- z3reload/trunk/ftests/dynamic.py (rev 0)
+++ z3reload/trunk/ftests/dynamic.py 2008-08-11 00:21:49 UTC (rev 89606)
@@ -0,0 +1,19 @@
+from zope.publisher.browser import BrowserView
+
+
+class SomeNumberView(BrowserView):
+
+ def __init__(self, context, request):
+ BrowserView.__init__(self, context, request)
+ self.number = 1001
+
+
+class AnotherNumberView(BrowserView):
+
+ number = 2001
+
+
+class ThirdNumberView(BrowserView):
+
+ def __call__(self):
+ return '3001'
Property changes on: z3reload/trunk/ftests/dynamic.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: z3reload/trunk/ftests/dynamic_orig.py
===================================================================
--- z3reload/trunk/ftests/dynamic_orig.py (rev 0)
+++ z3reload/trunk/ftests/dynamic_orig.py 2008-08-11 00:21:49 UTC (rev 89606)
@@ -0,0 +1,19 @@
+from zope.publisher.browser import BrowserView
+
+
+class SomeNumberView(BrowserView):
+
+ def __init__(self, context, request):
+ BrowserView.__init__(self, context, request)
+ self.number = 1001
+
+
+class AnotherNumberView(BrowserView):
+
+ number = 2001
+
+
+class ThirdNumberView(BrowserView):
+
+ def __call__(self):
+ return '3001'
Property changes on: z3reload/trunk/ftests/dynamic_orig.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: z3reload/trunk/ftests/ftesting.zcml
===================================================================
--- z3reload/trunk/ftests/ftesting.zcml (rev 0)
+++ z3reload/trunk/ftests/ftesting.zcml 2008-08-11 00:21:49 UTC (rev 89606)
@@ -0,0 +1,23 @@
+<configure xmlns="http://namespaces.zope.org/browser"
+ xmlns:reload="http://namespaces.pov.lt/z3reload">
+
+ <reload:reload modules="z3reload.ftests.dynamic" />
+
+ <page for="zope.traversing.interfaces.IContainmentRoot"
+ name="some.html"
+ class=".dynamic.SomeNumberView"
+ template="dynamic.pt"
+ permission="zope.Public" />
+
+ <page for="zope.traversing.interfaces.IContainmentRoot"
+ name="another.html"
+ class=".dynamic.AnotherNumberView"
+ template="dynamic.pt"
+ permission="zope.Public" />
+
+ <page for="zope.traversing.interfaces.IContainmentRoot"
+ name="third.txt"
+ class=".dynamic.ThirdNumberView"
+ permission="zope.Public" />
+
+</configure>
Property changes on: z3reload/trunk/ftests/ftesting.zcml
___________________________________________________________________
Name: svn:eol-style
+ native
Added: z3reload/trunk/ftests/reload.txt
===================================================================
--- z3reload/trunk/ftests/reload.txt (rev 0)
+++ z3reload/trunk/ftests/reload.txt 2008-08-11 00:21:49 UTC (rev 89606)
@@ -0,0 +1,73 @@
+z3reload functional test
+========================
+
+
+Quick look
+----------
+
+First, let's simply try looking at the views, to make sure that
+the configuration is correct.
+
+ >>> print http("GET /some.html HTTP/1.1\n", handle_errors=False)
+ HTTP/1.1 200 Ok
+ ...
+ <p>The number is 1001</p>
+ <BLANKLINE>
+
+ >>> print http("GET /another.html HTTP/1.1\n", handle_errors=False)
+ HTTP/1.1 200 Ok
+ ...
+ <p>The number is 2001</p>
+ <BLANKLINE>
+
+ >>> print http("GET /third.txt HTTP/1.1\n", handle_errors=False)
+ HTTP/1.1 200 Ok
+ ...
+ Content-Type: text/plain;charset=utf-8
+ <BLANKLINE>
+ 3001
+
+
+Modification
+------------
+
+Now we will modify the view code:
+
+ >>> from z3reload.ftests.tests import openfile
+ >>> import time
+ >>> def replace(old, new):
+ ... time.sleep(0.5) # longish, but shorter delays don't seem to work
+ ... orig = openfile('dynamic_orig.py').read()
+ ... f = openfile('dynamic.py', 'w')
+ ... f.write(orig.replace(old, new))
+ ... f.close()
+ ... time.sleep(0.5) # longish, but shorter delays don't seem to work
+
+ >>> replace('1001', '1123')
+ >>> print http("GET /some.html HTTP/1.1\n", handle_errors=False)
+ HTTP/1.1 200 Ok
+ ...
+ <p>The number is 1123</p>
+ <BLANKLINE>
+
+ >>> replace('2001', '2123')
+ >>> print http("GET /another.html HTTP/1.1\n", handle_errors=False)
+ HTTP/1.1 200 Ok
+ ...
+ <p>The number is 2123</p>
+ <BLANKLINE>
+
+ >>> replace('3001', '3123')
+
+ >>> print http("GET /third.txt HTTP/1.1\n", handle_errors=False)
+ HTTP/1.1 200 Ok
+ ...
+ Content-Type: text/plain;charset=utf-8
+ <BLANKLINE>
+ 3123
+
+
+Epilogue
+--------
+
+ vim: ft=rest
Property changes on: z3reload/trunk/ftests/reload.txt
___________________________________________________________________
Name: svn:eol-style
+ native
Added: z3reload/trunk/ftests/tests.py
===================================================================
--- z3reload/trunk/ftests/tests.py (rev 0)
+++ z3reload/trunk/ftests/tests.py 2008-08-11 00:21:49 UTC (rev 89606)
@@ -0,0 +1,27 @@
+import os
+import unittest
+
+from zope.testing import doctest
+from zope.app.testing.functional import FunctionalDocFileSuite
+
+
+current_dir = os.path.dirname(__file__)
+
+def openfile(filename, mode='r'):
+ return file(os.path.join(current_dir, filename), mode)
+
+def resetFile(test):
+ dynamic_orig = openfile('dynamic_orig.py').read()
+ openfile('dynamic.py', 'w').write(dynamic_orig)
+
+
+def test_suite():
+ optionflags = (doctest.ELLIPSIS | doctest.REPORT_NDIFF |
+ doctest.NORMALIZE_WHITESPACE |
+ doctest.REPORT_ONLY_FIRST_FAILURE)
+ return FunctionalDocFileSuite('reload.txt', optionflags=optionflags,
+ setUp=resetFile, tearDown=resetFile)
+
+
+if __name__ == '__main__':
+ unittest.main(defaultTest='test_suite')
Property changes on: z3reload/trunk/ftests/tests.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: z3reload/trunk/meta.zcml
===================================================================
--- z3reload/trunk/meta.zcml (rev 0)
+++ z3reload/trunk/meta.zcml 2008-08-11 00:21:49 UTC (rev 89606)
@@ -0,0 +1,11 @@
+<configure xmlns="http://namespaces.zope.org/meta">
+
+ <directives namespace="http://namespaces.pov.lt/z3reload">
+ <directive
+ name="reload"
+ schema=".metadirectives.IReloadDirective"
+ handler=".metaconfigure.reload"
+ />
+ </directives>
+
+</configure>
Property changes on: z3reload/trunk/meta.zcml
___________________________________________________________________
Name: svn:eol-style
+ native
Added: z3reload/trunk/metaconfigure.py
===================================================================
--- z3reload/trunk/metaconfigure.py (rev 0)
+++ z3reload/trunk/metaconfigure.py 2008-08-11 00:21:49 UTC (rev 89606)
@@ -0,0 +1,23 @@
+import sys
+from zope.configuration.exceptions import ConfigurationError
+
+
+enabled_classes = [] # list of class objects
+enabled_modules = [] # list of module names
+enabled_packages = [] # list of package names
+
+
+def handle_reload(classes, modules, packages):
+ """Add provided objects to global registry of reloadable objects."""
+ enabled_classes.extend(classes)
+ enabled_modules.extend([mod.__name__ for mod in modules])
+ enabled_packages.extend([pkg.__name__ for pkg in packages])
+
+
+def reload(_context, classes=[], modules=[], packages=[]):
+ """Process the `reload` ZCML directive."""
+ if not (classes or modules or packages):
+ raise ConfigurationError("You must specify at least one of"
+ " `classes`, `modules` or `packages`.")
+ _context.action(discriminator=None, callable=handle_reload,
+ args=(classes, modules, packages))
Property changes on: z3reload/trunk/metaconfigure.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: z3reload/trunk/metadirectives.py
===================================================================
--- z3reload/trunk/metadirectives.py (rev 0)
+++ z3reload/trunk/metadirectives.py 2008-08-11 00:21:49 UTC (rev 89606)
@@ -0,0 +1,40 @@
+from zope.interface import Interface
+from zope.configuration.fields import Tokens, GlobalObject
+
+
+class IReloadDirective(Interface):
+
+ classes = Tokens(
+ title=u"View classes",
+ required=False,
+ value_type=GlobalObject(
+ title=u"View class",
+ description=u"""
+ A view class for which automatic reload should be enabled.
+ """))
+
+ modules = Tokens(
+ title=u"Modules",
+ required=False,
+ value_type=GlobalObject(
+ title=u"Module",
+ description=u"""
+ A module containing views for which automatic reload should be
+ enabled.
+ """))
+
+ packages = Tokens(
+ title=u"Packages",
+ required=False,
+ value_type=GlobalObject(
+ title=u"Package",
+ description=u"""
+ A package containing views for which automatic reload should be
+ enabled.
+
+ `module` only works for a single module, whereas `package` also
+ applies for contained modules and packages.
+ """))
+
+
+# TODO: reload:omit, reload:all
Property changes on: z3reload/trunk/metadirectives.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: z3reload/trunk/package-includes/z3reload-configure.zcml
===================================================================
--- z3reload/trunk/package-includes/z3reload-configure.zcml (rev 0)
+++ z3reload/trunk/package-includes/z3reload-configure.zcml 2008-08-11 00:21:49 UTC (rev 89606)
@@ -0,0 +1,14 @@
+<configure xmlns="http://namespaces.zope.org/zope"
+ xmlns:reload="http://namespaces.pov.lt/z3reload">
+
+<include package="z3reload" />
+
+<!-- site-specific configuration -->
+
+<!-- example:
+<reload:reload
+ modules="zope.app.demo.jobboard.browser"
+ packages="zope.app.demo.hellopackage" />
+-->
+
+</configure>
Property changes on: z3reload/trunk/package-includes/z3reload-configure.zcml
___________________________________________________________________
Name: svn:eol-style
+ native
Added: z3reload/trunk/package-includes/z3reload-ftesting.zcml
===================================================================
--- z3reload/trunk/package-includes/z3reload-ftesting.zcml (rev 0)
+++ z3reload/trunk/package-includes/z3reload-ftesting.zcml 2008-08-11 00:21:49 UTC (rev 89606)
@@ -0,0 +1 @@
+<include package="z3reload.ftests" file="ftesting.zcml" />
Property changes on: z3reload/trunk/package-includes/z3reload-ftesting.zcml
___________________________________________________________________
Name: svn:eol-style
+ native
Added: z3reload/trunk/package-includes/z3reload-meta.zcml
===================================================================
--- z3reload/trunk/package-includes/z3reload-meta.zcml (rev 0)
+++ z3reload/trunk/package-includes/z3reload-meta.zcml 2008-08-11 00:21:49 UTC (rev 89606)
@@ -0,0 +1 @@
+<include package="z3reload" file="meta.zcml"/>
Property changes on: z3reload/trunk/package-includes/z3reload-meta.zcml
___________________________________________________________________
Name: svn:eol-style
+ native
Added: z3reload/trunk/reload.py
===================================================================
--- z3reload/trunk/reload.py (rev 0)
+++ z3reload/trunk/reload.py 2008-08-11 00:21:49 UTC (rev 89606)
@@ -0,0 +1,81 @@
+import sys
+from zope.app.pagetemplate.simpleviewclass import simple as SimplePTPage
+from zope.app.publisher.browser.viewmeta import simple as SimplePage
+
+from z3reload import BLATHER
+
+simple_view_classes = (SimplePTPage, SimplePage)
+
+
+class Reloader(object):
+ """A mixin to be used on SimpleViewClass instances.
+
+ These SimpleViewClass instances are in fact dynamically constructed types
+ with either zope.app.pagetemplate.simpleviewclass.simple or
+ zope.app.publisher,browser.viewmeta as one of the base classes.
+
+ This mixin must be the first superclass, because its __init__ must be
+ called on instantiation.
+ """
+
+ def __init__(self, *args, **kw):
+ bases = self.__class__.__bases__
+ assert len(bases) >= 2
+ reloader2, real_view = bases[:2]
+ assert reloader2 is Reloader
+
+ rest = bases[2:]
+# for c in rest:
+# if c not in simple_view_classes:
+# print >> sys.stderr, "Warning: %r has base %r" % (self.__class__, c)
+
+ clsname = real_view.__name__
+ modname = real_view.__module__
+ module = sys.modules[modname]
+ if hasattr(module, clsname):
+ reload(module)
+ new_view = getattr(module, clsname)
+ self.__class__.__bases__ = (Reloader, new_view) + rest
+ else:
+ # If the module does not have such an attribute, chances are that
+ # the class was dynamically constructed. In this case reloading is
+ # likely to break so we don't do it.
+ new_view = real_view # just use the old view
+
+ self.__sanitize_bases(new_view)
+ new_view.__init__(self, *args, **kw)
+
+ def __sanitize_bases(self, cls):
+ """Make sure that the bases of a class are in the scope.
+
+ This works around the problem when a class bases do not correspond
+ to the same-named classes in the scope, which could happen after a
+ reload. This causes errors when invoking base clases
+ """
+ modname = cls.__module__
+ module = sys.modules[modname]
+
+ bases = cls.__bases__
+ new_bases = []
+ for b in bases:
+ bc = getattr(module, b.__name__, None)
+ if bc is not None and bc.__module__ != modname:
+ # Make sure that the base class comes from a different module.
+ new_base = getattr(module, b.__name__)
+ self.__sanitize_bases(new_base)
+ new_bases.append(new_base)
+ else:
+ # Couldn't find class in local scope, abort.
+ break
+ else:
+ # All bases checked successfully.
+ cls.__bases__ = tuple(new_bases)
+
+
+
+def install_reloader(view_class):
+ """Install the Reloader mixin on view_class."""
+ assert view_class.__bases__[-1] in simple_view_classes
+ if BLATHER:
+ print >> sys.stderr, 'Reloader installed for', view_class.__bases__[0]
+ view_class.__bases__ = (Reloader, ) + view_class.__bases__
Property changes on: z3reload/trunk/reload.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Added: z3reload/trunk/subscriber.py
===================================================================
--- z3reload/trunk/subscriber.py (rev 0)
+++ z3reload/trunk/subscriber.py 2008-08-11 00:21:49 UTC (rev 89606)
@@ -0,0 +1,52 @@
+import sys
+
+from zope.component import getGlobalSiteManager
+from zope.publisher.interfaces import IRequest
+from zope.component.registry import AdapterRegistration
+
+from z3reload.reload import install_reloader, simple_view_classes
+from z3reload.metaconfigure import enabled_classes, enabled_modules
+from z3reload.metaconfigure import enabled_packages
+
+request_type = IRequest
+
+
+def is_simple_view(reg):
+ """Return True if reg is a registration for a `simple` view.
+
+ A `simple` view is one that subclasses one of simple_view_classes.
+ """
+ if not (isinstance(reg, AdapterRegistration) and
+ len(reg.required) > 0 and
+ reg.required[-1] is not None and
+ reg.required[-1].isOrExtends(IRequest)):
+ return False # this registration does not appear to be a view
+
+ return (type(reg.factory) == type and
+ issubclass(reg.factory, simple_view_classes))
+
+
+def reload_enabled_for(view_class):
+ """Return True if view_class should be made reloadable."""
+ assert view_class.__bases__[-1] in simple_view_classes
+ real_view = view_class.__bases__[0]
+ if real_view in enabled_classes:
+ return True
+ for module in enabled_modules:
+ if real_view.__module__ == module:
+ return True
+ for package in enabled_packages:
+ if real_view.__module__.startswith(package):
+ return True
+ return False
+
+
+def database_opened(event):
+ """Scan adapter registrations and make specified views reloadable.
+
+ Hooks on the DatabaseOpened event.
+ """
+ gsm = getGlobalSiteManager()
+ for reg in gsm.registeredAdapters():
+ if is_simple_view(reg) and reload_enabled_for(reg.factory):
+ install_reloader(reg.factory)
Property changes on: z3reload/trunk/subscriber.py
___________________________________________________________________
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
More information about the Checkins
mailing list