trac-1.0.9+dfsg/0000755000175000017500000000000012632414422011525 5ustar wmbwmbtrac-1.0.9+dfsg/UPGRADE0000644000175000017500000005011412574541635012554 0ustar wmbwmb.. charset=utf-8 Upgrade Instructions ==================== #. Instructions #. 1. Bring your server off-line #. 2. Update the Trac Code #. 3. Upgrade the Trac Environment #. 4. Update the Trac Documentation #. 5. Refresh static resources #. 6. Steps specific to a given Trac version #. Upgrading from Trac 0.12 to Trac 1.0 #. Upgrading from Trac 0.11 to Trac 0.12 #. Upgrading from Trac 0.10 to Trac 0.11 #. 7. Restart the Web Server #. Known Issues #. Customized Templates #. ZipImportError #. Wiki Upgrade #. Trac database upgrade #. Parent dir #. Related topics #. Upgrading Python #. Windows and Python 2.6 #. Changing Database Backend #. Upgrading from older versions of Trac Instructions ------------ Typically, there are seven steps involved in upgrading to a newer version of Trac: 1. Bring your server off-line ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ It is not a good idea to update a running server: the server processes may have parts of the current packages cached in memory, and updating the code will likely trigger `internal errors`_. Although a database backup will be implicitly created by default when upgrading the environment, it is always a good idea to perform a full backup of the environment using the `hotcopy`_ command before beginning. 2. Update the Trac Code ~~~~~~~~~~~~~~~~~~~~~~~ Get the new version as described in `TracInstall`_, or your operating system specific procedure. If you already have a 0.12 version of Trac installed via easy_install , it might be easiest to also use easy_install to upgrade your Trac installation: :: easy_install --upgrade Trac==1.0 If you do a manual (not operating system-specific) upgrade, you should also stop any running Trac servers before the installation. Doing "hot" upgrades is not advised, especially on Windows (`#7265`_). You may also want to remove the pre-existing Trac code by deleting the trac directory from the Python lib/site-packages directory, or remove Trac .egg files from former versions. The location of the site- packages directory depends on the operating system and the location in which Python was installed. However, the following locations are typical: + on Linux: /usr/lib/python2.X/site-packages + on Windows: C:\Python2.X\lib\site-packages + on MacOSX: /Library/Python/2.X/site-packages You may also want to remove the Trac cgi-bin , htdocs , templates and wiki-default directories that are commonly found in a directory called share/trac . The exact location depends on your platform. This cleanup is not mandatory, but makes it easier to troubleshoot issues later on, as your installation is uncluttered by code or templates from a previous release that is not used anymore. As usual, make a backup before actually removing things. 3. Upgrade the Trac Environment ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Environment upgrades are not necessary for minor version releases unless otherwise noted. After restarting, Trac should show the instances which need a manual upgrade via the automated upgrade scripts to ease the pain. These scripts are run via `trac-admin`_: :: trac-admin /path/to/projenv upgrade This command will do nothing if the environment is already up-to-date. Note that a backup of your database will be performed automatically prior to the upgrade. This feature is relatively new for PostgreSQL or MySQL databases, so if it fails, you will have to backup the database manually. Then, to perform the actual upgrade, run: :: trac-admin /path/to/projenv upgrade --no-backup 4. Update the Trac Documentation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ By default, every `Trac environment`_ includes a copy of the Trac documentation for the installed version. However, to keep the included documentation in sync with the installed version of Trac, use the following `trac-admin`_ command to upgrade the documentation: :: trac-admin /path/to/projenv wiki upgrade Note that this procedure will leave your WikiStart page intact. 5. Refresh static resources ~~~~~~~~~~~~~~~~~~~~~~~~~~~ If you have set up a web server to give out static resources directly (accessed using the /chrome/ URL) then you will need to refresh them using the same command: :: trac-admin /path/to/env deploy /deploy/path this will extract static resources and CGI scripts ( trac.wsgi , etc) from new Trac version and its plugins into /deploy/path . Some web browsers (IE, Opera) cache CSS and Javascript files aggressively, so you may need to instruct your users to manually erase the contents of their browser's cache, a forced refreshed ( ) should be enough. 6. Steps specific to a given Trac version ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Upgrading from Trac 0.12 to Trac 1.0 ```````````````````````````````````` Python 2.4 no longer supported ++++++++++++++++++++++++++++++ Upgrade Python to at least 2.5, but not 3.0. Obsolete Plugins ++++++++++++++++ Trac has added functionality equivalent to the following plugins: + `BatchModifyPlugin`_ + ​`GitPlugin`_ + `OverrideEditPlugin`_ The plugins should be removed when upgrading Trac to 1.0. Subversion components not enabled by default for new installations ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ The Trac components for Subversion support are no longer enabled by default. To enable the svn support, you need to make sure the tracopt.versioncontrol.svn components are enabled, for example by setting the following in the `TracIni`_: :: [components] tracopt.versioncontrol.svn.* = enabled The upgrade procedure should take care of this and change the `TracIni`_ appropriately, unless you already had the svn components explicitly disabled. Attachments migrated to new location ++++++++++++++++++++++++++++++++++++ Another step in the automatic upgrade will change the way the attachments are stored. Create a backup of the attachments directory before upgrading. In case the attachments directory contains some files which are *not* attachments, the last step of the migration to the new layout will fail: the deletion of the now unused attachments directory can't be done if there are still files and folders in it. You may ignore this error, but better to move them elsewhere and remove the attachments directory manually. The attachments themselves are now all located in your environment below the files/attachments directory. Behavior of [ticket] default_owner changed ++++++++++++++++++++++++++++++++++++++++++ Prior to 1.0, the owner field of new tickets always defaulted to [ticket] default_owner when the value was not empty. If the value was empty, the owner field defaulted to to the Component's owner. In 1.0 and later, the default_owner must be set to < default > to make new tickets default to the Component's owner. This change allows the default_owner to be set to an empty value if no default owner is desired. Upgrading from Trac 0.11 to Trac 0.12 ````````````````````````````````````` Python 2.3 no longer supported ++++++++++++++++++++++++++++++ The minimum supported version of Python is now 2.4. SQLite v3.x required ++++++++++++++++++++ SQLite v2.x is no longer supported. If you still use a Trac database of this format, you'll need to convert it to SQLite v3.x first. See `PySqlite#UpgradingSQLitefrom2.xto3.x`_ for details. `PySqlite`_ 2 required ++++++++++++++++++++++ `PySqlite`_ 1.1.x is no longer supported. Please install 2.5.5 or later if possible, see `Trac database upgrade`_ below. Obsolete Plugins ++++++++++++++++ Trac has added functionality equivalent to the following plugins: + `AutoQueryPlugin`_ + ​`AdminConsoleProviderPatch`_ + `AnchorMacro`_: see `WikiFormatting#SettingAnchors`_ + `TicketChangePlugin`_: see `TICKET_EDIT_COMMENT permission`_ + `TicketDeletePlugin`_: see tracopt.ticket.deleter + `SubversionLocationPlugin`_: see `TracRepositoryAdmin#Repositories`_ + `WikiCreoleRendererPlugin`_: see `WikiCreole`_ + `RepoRevisionSyntaxPlugin`_ (added in 0.12.1) The plugins should be removed when upgrading Trac to 0.12. Multiple Repository Support +++++++++++++++++++++++++++ The latest version includes support for multiple repositories. If you plan to add more repositories to your Trac instance, please refer to `TracRepositoryAdmin#Migration`_. This may be of interest to users with only one repository, since there is now a way to avoid the potentially costly resync check at every request. Resynchronize the Trac Environment Against the Source Code Repository +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Each `Trac environment`_ must be resynchronized against the source code repository in order to avoid errors such as "`No changeset ??? in the repository`_" while browsing the source through the Trac interface: :: trac-admin /path/to/projenv repository resync '*' Improved repository synchronization +++++++++++++++++++++++++++++++++++ In addition to supporting multiple repositories, there is now a more efficient method for synchronizing Trac and your repositories. While you can keep the same synchronization as in 0.11 adding the post-commit hook as outlined in `TracRepositoryAdmin#Synchronization`_ and `TracRepositoryAdmin#ExplicitSync`_ will allow more efficient synchronization and is more or less required for multiple repositories. Note that if you were using the trac-post-commit-hook , *you're strongly advised to upgrade it* to the new hook documented in the above references and `here`_, as the old hook will not work with anything else than the default repository and even for this case, it won't trigger the appropriate notifications. Authz permission checking +++++++++++++++++++++++++ The authz permission checking has been migrated to a fine-grained permission policy. If you use authz permissions (aka [trac] authz_file and authz_module_name ), you must add AuthzSourcePolicy in front of your permission policies in [trac] permission_policies . You must also remove BROWSER_VIEW , CHANGESET_VIEW , FILE_VIEW and LOG_VIEW from your global permissions with trac-admin $ENV permission remove or the "Permissions" admin panel. Microsecond timestamps ++++++++++++++++++++++ All timestamps in database tables, except the session table, have been changed from "seconds since epoch" to "microseconds since epoch" values. This change should be transparent to most users, except for custom reports. If any of your reports use date/time columns in calculations (e.g. to pass them to datetime() ), you must divide the values retrieved from the database by 1'000'000. Similarly, if a report provides a calculated value to be displayed as a date/time (i.e. with a column named "time", "datetime", "changetime", "date", "created" or "modified"), you must provide a microsecond timestamp, that is, multiply your previous calculation with 1'000'000. Upgrading from Trac 0.10 to Trac 0.11 ````````````````````````````````````` Site Templates and Styles +++++++++++++++++++++++++ The templating engine has changed in 0.11 to Genshi, please look at `TracInterfaceCustomization`_ for more information. If you are using custom CSS or modified templates in the templates directory of the `TracEnvironment`_, you will need to convert them to the Genshi way of doing things. To continue to use your style sheet, follow the instructions at `TracInterfaceCustomization#SiteAppearance`_. Trac Macros, Plugins ++++++++++++++++++++ The Trac macros will need to be adapted, as the old-style wiki-macros are not supported anymore due to the drop of `ClearSilver`_ and the HDF. They need to be converted to the new-style macros, see `WikiMacros`_. When they are converted to the new style, they need to be placed into the plugins directory instead and not wiki-macros, which is no longer scanned for macros or plugins. For FCGI/WSGI/CGI users +++++++++++++++++++++++ For those who run Trac under the CGI environment, run this command in order to obtain the trac.*gi file: :: trac-admin /path/to/env deploy /deploy/directory/path This will create a deploy directory with the following two subdirectories: cgi-bin and htdocs . Then update your Apache configuration file httpd.conf with this new trac.cgi location and htdocs location. Web Admin plugin integrated +++++++++++++++++++++++++++ If you had the `WebAdmin`_ plugin installed, you can uninstall it as it is part of the Trac code base since 0.11. New Default Configurable Workflow +++++++++++++++++++++++++++++++++ When you run trac-admin upgrade , your trac.ini will be modified to include a [ticket-workflow] section. The workflow configured in this case is the original workflow, so that ticket actions will behave like they did in 0.10. Graphically, that looks like this: Enable JavaScript to display the workflow graph. There are some significant caveats in this, such as accepting a ticket sets it to 'assigned' state, and assigning a ticket sets it to 'new' state. So you will probably want to migrate to "basic" workflow; `contrib/workflow/migrate_original_to_basic.py`_ may be helpful. See `TracWorkflow`_ for a detailed description of the new basic workflow. Global Configuration ++++++++++++++++++++ In versions prior to 0.11, the global configuration was by default located in $prefix/share/trac/conf/trac.ini or /etc/trac/trac.ini , depending on the distribution. You may want to specify that file to inherit from when upgrading. Literally, when upgrading you have to add an [inherit] section to your project's trac.ini file. Additionally, you have to move your customized templates and common images from $prefix/share/trac/... to the new location. 7. Restart the Web Server ~~~~~~~~~~~~~~~~~~~~~~~~~ If you are not running `CGI`_, reload the new Trac code by restarting your web server. Known Issues ------------ Customized Templates ~~~~~~~~~~~~~~~~~~~~ Trac supports customization of its Genshi templates by placing copies of the templates in the /templates folder of your `environment`_ or in a common location specified in the ` [inherit] templates_dir`_ configuration setting. If you choose to do so, be aware that you will need to repeat your changes manually on a copy of the new templates when you upgrade to a new release of Trac (even a minor one), as the templates will likely evolve. So keep a diff around. The preferred way to perform `TracInterfaceCustomization`_ is to write a custom plugin doing an appropriate ITemplateStreamFilter transformation, as this is more robust in case of changes: we usually won't modify element id s or change CSS class es, and if we have to do so, this will be documented in the `TracDev/ApiChanges`_ pages. ZipImportError ~~~~~~~~~~~~~~ Due to internal caching of zipped packages, whenever the content of the packages change on disk, the in-memory zip index will no longer match and you'll get irrecoverable ZipImportError errors. Better anticipate and bring your server down for maintenance before upgrading. See `#7014`_ for details. Wiki Upgrade ~~~~~~~~~~~~ trac-admin will not delete or remove default wiki pages that were present in a previous version but are no longer in the new version. Trac database upgrade ~~~~~~~~~~~~~~~~~~~~~ A known issue in some versions of `PySqlite`_ (2.5.2-2.5.4) prevents the trac-admin upgrade script from successfully upgrading the database format. It is advised to use either a newer or older version of the sqlite python bindings to avoid this error. For more details see ticket `#9434`_. Parent dir ~~~~~~~~~~ If you use a Trac parent env configuration and one of the plugins in one child does not work, none of the children will work. Related topics -------------- Upgrading Python ~~~~~~~~~~~~~~~~ Upgrading Python to a newer version will require reinstallation of Python packages: Trac itself of course, but also `easy_install`_, if you've been using that. If you are using Subversion, you'll also need to upgrade the Python bindings for svn. Windows and Python 2.6 `````````````````````` If you've been using CollabNet's Subversion package, you may need to uninstall that in favor of `Alagazam`_, which has the Python bindings readily available, see `TracSubversion`_. That package works without tweaking. Changing Database Backend ~~~~~~~~~~~~~~~~~~~~~~~~~ The `TracMigratePlugin`_ on `trac-hacks.org`_ has been written to assist in migrating between SQLite, MySQL and PostgreSQL databases. Upgrading from older versions of Trac ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ For upgrades from versions older than Trac 0.10, refer first to `wiki:0.10/TracUpgrade#SpecificVersions`_. See also: `TracGuide`_, `TracInstall`_ .. _ [inherit] templates_dir: http://trac.edgewall.org/wiki/TracIni#GlobalConfiguration .. _#7014: http://trac.edgewall.org/intertrac/%237014 .. _#7265: http://trac.edgewall.org/intertrac/%237265 .. _#9434: http://trac.edgewall.org/intertrac/%239434 .. _AdminConsoleProviderPatch: https://trac-hacks.org/wiki/AdminConsoleProviderPatch .. _Alagazam: http://alagazam.net/ .. _AnchorMacro: https://trac-hacks.org/wiki/AnchorMacro .. _AutoQueryPlugin: https://trac-hacks.org/wiki/AutoQueryPlugin .. _BatchModifyPlugin: https://trac-hacks.org/wiki/BatchModifyPlugin .. _CGI: http://trac.edgewall.org/wiki/TracCgi .. _ClearSilver: http://trac.edgewall.org/intertrac/ClearSilver .. _contrib/workflow/migrate_original_to_basic.py: http://trac.edgewall.org/intertrac/source%3Atrunk/contrib/workflow/migrate_original_to_basic.py .. _easy_install: http://pypi.python.org/pypi/setuptools .. _environment: http://trac.edgewall.org/wiki/TracEnvironment .. _GitPlugin: https://trac-hacks.org/wiki/GitPlugin .. _here: http://trac.edgewall.org/wiki/TracWorkflow#Howtocombinethetracopt.ticket.commit_updaterwiththetestingworkflow .. _hotcopy: http://trac.edgewall.org/wiki/TracBackup .. _internal errors: http://trac.edgewall.org/wiki/TracUpgrade#ZipImportError .. _No changeset ??? in the repository: http://trac.edgewall.org/intertrac/%236120 .. _OverrideEditPlugin: https://trac-hacks.org/wiki/OverrideEditPlugin .. _PySqlite#UpgradingSQLitefrom2.xto3.x: http://trac.edgewall.org/intertrac/PySqlite%23UpgradingSQLitefrom2.xto3.x .. _PySqlite: http://trac.edgewall.org/intertrac/PySqlite .. _RepoRevisionSyntaxPlugin: https://trac-hacks.org/wiki/RepoRevisionSyntaxPlugin .. _SubversionLocationPlugin: https://trac-hacks.org/wiki/SubversionLocationPlugin .. _TICKET_EDIT_COMMENT permission: http://trac.edgewall.org/wiki/TracPermissions#TicketSystem .. _TicketChangePlugin: https://trac-hacks.org/wiki/TicketChangePlugin .. _TicketDeletePlugin: https://trac-hacks.org/wiki/TicketDeletePlugin .. _Trac database upgrade: http://trac.edgewall.org/wiki/TracUpgrade#Tracdatabaseupgrade .. _Trac environment: http://trac.edgewall.org/wiki/TracEnvironment .. _trac-admin: http://trac.edgewall.org/wiki/TracAdmin .. _trac-hacks.org: http://trac-hacks.org .. _TracDev/ApiChanges: http://trac.edgewall.org/intertrac/TracDev/ApiChanges .. _TracEnvironment: http://trac.edgewall.org/wiki/TracEnvironment .. _TracGuide: http://trac.edgewall.org/wiki/TracGuide .. _TracIni: http://trac.edgewall.org/wiki/TracIni .. _TracInstall: http://trac.edgewall.org/wiki/TracInstall .. _TracInterfaceCustomization#SiteAppearance: http://trac.edgewall.org/wiki/TracInterfaceCustomization#SiteAppearance .. _TracInterfaceCustomization: http://trac.edgewall.org/wiki/TracInterfaceCustomization .. _TracMigratePlugin: http://trac-hacks.org/wiki/TracMigratePlugin .. _TracRepositoryAdmin#ExplicitSync: http://trac.edgewall.org/wiki/TracRepositoryAdmin#ExplicitSync .. _TracRepositoryAdmin#Migration: http://trac.edgewall.org/wiki/TracRepositoryAdmin#Migration .. _TracRepositoryAdmin#Repositories: http://trac.edgewall.org/wiki/TracRepositoryAdmin#Repositories .. _TracRepositoryAdmin#Synchronization: http://trac.edgewall.org/wiki/TracRepositoryAdmin#Synchronization .. _TracSubversion: http://trac.edgewall.org/intertrac/TracSubversion .. _TracWorkflow: http://trac.edgewall.org/wiki/TracWorkflow .. _WebAdmin: http://trac.edgewall.org/intertrac/WebAdmin .. _wiki:0.10/TracUpgrade#SpecificVersions: http://trac.edgewall.org/intertrac/wiki%3A0.10/TracUpgrade%23SpecificVersions .. _WikiCreole: http://trac.edgewall.org/intertrac/WikiCreole .. _WikiCreoleRendererPlugin: https://trac-hacks.org/wiki/WikiCreoleRendererPlugin .. _WikiFormatting#SettingAnchors: http://trac.edgewall.org/wiki/WikiFormatting#SettingAnchors .. _WikiMacros: http://trac.edgewall.org/wiki/WikiMacros trac-1.0.9+dfsg/RELEASE0000644000175000017500000000235112574541635012545 0ustar wmbwmbRelease Notes for Trac 1.0 'Cell' Release ========================================= September 7, 2012 Highlights ---------- * Refreshed user interface * Git support * Branching structure displayed in the revision log * Ticket batch modification support Detailed User Visible Changes ----------------------------- A detailed view of the API changes since 0.12.x can be found in http://trac.edgewall.org/wiki/TracDev/ReleaseNotes/1.0 Developer-visible changes ------------------------- A detailed view of the API changes since 0.12.x can be found in http://trac.edgewall.org/wiki/TracDev/ApiChanges/1.0 Acknowledgements ---------------- Many thanks to the growing number of people who have, and continue to, support the project. Also our thanks to all people providing feedback and bug reports that helps us making Trac better, easier to use and more effective. Starting with the 0.12 release, special thanks go to our many translators. Without your invaluable help, Trac would not evolve. Thank you all. Finally, we offer hope that Trac will prove itself useful to like-minded programmers around the world, and that this release will prove an improvement over the last version. Please let us know. :-) /The Trac Team http://trac.edgewall.org/ trac-1.0.9+dfsg/sample-plugins/0000755000175000017500000000000012574541731014476 5ustar wmbwmbtrac-1.0.9+dfsg/sample-plugins/HelloWorld.py0000644000175000017500000000531712574541640017130 0ustar wmbwmb# -*- coding: utf-8 -*- # # Copyright (C) 2007-2013 Edgewall Software # Copyright (C) 2007 Christian Boos # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://trac.edgewall.org/. """Example macro.""" revision = "$Rev: 12412 $" url = "$URL: http://svn.edgewall.org/repos/trac/branches/1.0-stable/sample-plugins/HelloWorld.py $" # # The following shows the code for macro, old-style. # # The `execute` function serves no purpose other than to illustrate # the example, it will not be used anymore. # # ---- (ignore in your own macro) ---- # -- from trac.util import escape from trac.util.translation import cleandoc_ def execute(hdf, txt, env): # Currently hdf is set only when the macro is called # From a wiki page if hdf: hdf['wiki.macro.greeting'] = 'Hello World' # args will be `None` if the macro is called without parenthesis. args = txt or 'No arguments' # then, as `txt` comes from the user, it's important to guard against # the possibility to inject malicious HTML/Javascript, by using `escape()`: return 'Hello World, args = ' + escape(args) # -- # ---- (ignore in your own macro) ---- # # The following is the converted new-style macro # # ---- (reuse for your own macro) ---- # -- from trac.wiki.macros import WikiMacroBase class HelloWorldMacro(WikiMacroBase): _description = cleandoc_( """Simple HelloWorld macro. Note that the name of the class is meaningful: - it must end with "Macro" - what comes before "Macro" ends up being the macro name The documentation of the class (i.e. what you're reading) will become the documentation of the macro, as shown by the !MacroList macro (usually used in the TracWikiMacros page). """) def expand_macro(self, formatter, name, content): """Return some output that will be displayed in the Wiki content. `name` is the actual name of the macro (no surprise, here it'll be `'HelloWorld'`), `content` is the text enclosed in parenthesis at the call of the macro. Note that if there are ''no'' parenthesis (like in, e.g. [[HelloWorld]]), then `content` is `None`. """ return 'Hello World, content = ' + unicode(content) # Note that there's no need to HTML escape the returned data, # as the template engine (Genshi) will do it for us. # -- # ---- (reuse for your own macro) ---- trac-1.0.9+dfsg/sample-plugins/permissions/0000755000175000017500000000000012574541731017051 5ustar wmbwmbtrac-1.0.9+dfsg/sample-plugins/permissions/debug_perm.py0000644000175000017500000000275012574541640021537 0ustar wmbwmb# -*- coding: utf-8 -*- # # Copyright (C) 2007-2013 Edgewall Software # Copyright (C) 2007 Christian Boos # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://trac.edgewall.org/. from trac.core import * from trac.perm import IPermissionPolicy, PermissionCache from trac.resource import Resource revision = "$Rev$" url = "$URL$" class DebugPolicy(Component): """Verify the well-formedness of the permission checks. **This plugin is only useful for Trac Development.** Once this plugin is enabled, you'll have to insert it at the appropriate place in your list of permission policies, e.g. {{{ [trac] permission_policies = DebugPolicy, SecurityTicketsPolicy, AuthzPolicy, DefaultPermissionPolicy, LegacyAttachmentPolicy }}} """ implements(IPermissionPolicy) # IPermissionPolicy methods def check_permission(self, action, username, resource, perm): if resource: assert resource is None or isinstance(resource, Resource) assert isinstance(perm, PermissionCache) self.log.info("does '%s' have %s on %r?", username, action, resource) trac-1.0.9+dfsg/sample-plugins/permissions/public_wiki_policy.py0000644000175000017500000000535012574541640023305 0ustar wmbwmb# -*- coding: utf-8 -*- # # Copyright (C) 2007-2013 Edgewall Software # Copyright (C) 2007 Christian Boos # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://trac.edgewall.org/. from fnmatch import fnmatchcase from trac.config import Option from trac.core import * from trac.perm import IPermissionPolicy revision = "$Rev: 12500 $" url = "$URL: http://svn.edgewall.org/repos/trac/branches/1.0-stable/sample-plugins/permissions/public_wiki_policy.py $" class PublicWikiPolicy(Component): """Allow public access to some wiki pages. This is a sample permission policy plugin illustrating how to check permission on realms. Don't forget to integrate that plugin in the appropriate place in the list of permission policies: {{{ [trac] permission_policies = PublicWikiPolicy, DefaultPermissionPolicy }}} Then you can configure which pages you want to make public: {{{ [public_wiki] view = Public* modify = PublicSandbox/* }}} """ implements(IPermissionPolicy) view = Option('public_wiki', 'view', 'Public*', """Case-sensitive glob pattern used for granting view permission on all Wiki pages matching it.""") modify = Option('public_wiki', 'modify', 'Public*', """Case-sensitive glob pattern used for granting modify permissions on all Wiki pages matching it.""") def check_permission(self, action, username, resource, perm): if resource: # fine-grained permission check if resource.realm == 'wiki': # wiki realm or resource if resource.id: # ... it's a resource if action == 'WIKI_VIEW': # (think 'VIEW' here) pattern = self.view else: pattern = self.modify if fnmatchcase(resource.id, pattern): return True else: # ... it's a realm return True # this policy ''may'' grant permissions on some wiki pages else: # coarse-grained permission check # # support for the legacy permission checks: no resource specified # and realm information in the action name itself. # if action.startswith('WIKI_'): return True # this policy ''may'' grant permissions on some wiki pages trac-1.0.9+dfsg/sample-plugins/permissions/vulnerability_tickets.py0000644000175000017500000000477112574541640024052 0ustar wmbwmb# -*- coding: utf-8 -*- # # Copyright (C) 2007-2013 Edgewall Software # Copyright (C) 2007 Alec Thomas # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://trac.edgewall.org/. from trac.core import * from trac.perm import IPermissionPolicy, IPermissionRequestor revision = "$Rev: 12164 $" url = "$URL: http://svn.edgewall.org/repos/trac/branches/1.0-stable/sample-plugins/permissions/vulnerability_tickets.py $" class SecurityTicketsPolicy(Component): """Prevent public access to security sensitive tickets. Add the VULNERABILITY_VIEW permission as a pre-requisite for any other permission check done on tickets that have the words "security" or "vulnerability" in the summary or keywords fields. Once this plugin is enabled, you'll have to insert it at the appropriate place in your list of permission policies, e.g. {{{ [trac] permission_policies = SecurityTicketsPolicy, AuthzPolicy, DefaultPermissionPolicy, LegacyAttachmentPolicy }}} """ implements(IPermissionPolicy, IPermissionRequestor) # IPermissionPolicy methods def check_permission(self, action, username, resource, perm): # We add the 'VULNERABILITY_VIEW' pre-requisite for any action # other than 'VULNERABILITY_VIEW' itself, as this would lead # to recursion. if action == 'VULNERABILITY_VIEW': return # Check whether we're dealing with a ticket resource while resource: if resource.realm == 'ticket': break resource = resource.parent if resource and resource.realm == 'ticket' and resource.id is not None: for keywords, summary in self.env.db_query( "SELECT keywords, summary FROM ticket WHERE id=%s", (resource.id,)): fields = ''.join(f for f in (keywords, summary) if f).lower() if 'security' in fields or 'vulnerability' in fields: if 'VULNERABILITY_VIEW' not in perm: return False # IPermissionRequestor methods def get_permission_actions(self): yield 'VULNERABILITY_VIEW' trac-1.0.9+dfsg/sample-plugins/Timestamp.py0000644000175000017500000000337512574541640017022 0ustar wmbwmb# -*- coding: utf-8 -*- # # Copyright (C) 2007-2013 Edgewall Software # Copyright (C) 2007 Christian Boos # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://trac.edgewall.org/. """Inserts the current time (in seconds) into the wiki page.""" revision = "$Rev: 12743 $" url = "$URL: http://svn.edgewall.org/repos/trac/branches/1.0-stable/sample-plugins/Timestamp.py $" # # The following shows the code for macro, old-style. # # The `execute` function serves no purpose other than to illustrate # the example, it will not be used anymore. # # ---- (ignore in your own macro) ---- # -- import time # Trac before version 0.11 was using `time` module def execute(hdf, txt, env): t = time.localtime() return "%s" % time.strftime('%c', t) # -- # ---- (ignore in your own macro) ---- # # The following is the converted new-style macro # # ---- (reuse for your own macro) ---- # -- from datetime import datetime # Note: since Trac 0.11, datetime objects are used internally from genshi.builder import tag from trac.util.datefmt import format_datetime, utc from trac.wiki.macros import WikiMacroBase class TimestampMacro(WikiMacroBase): _description = "Inserts the current time (in seconds) into the wiki page." def expand_macro(self, formatter, name, content, args=None): t = datetime.now(utc) return tag.strong(format_datetime(t, '%c')) # -- # ---- (reuse for your own macro) ---- trac-1.0.9+dfsg/sample-plugins/milestone_to_version.py0000644000175000017500000000576212574541640021327 0ustar wmbwmb# -*- coding: utf-8 -*- # # Copyright (C) 2009-2013 Edgewall Software # Copyright (C) 2009 Remy Blank # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://trac.edgewall.org/. import re from trac.config import Option from trac.core import * from trac.resource import ResourceNotFound from trac.ticket.api import IMilestoneChangeListener from trac.ticket.model import Version revision = "$Rev: 13230 $" url = "$URL: http://svn.edgewall.org/repos/trac/branches/1.0-stable/sample-plugins/milestone_to_version.py $" class MilestoneToVersion(Component): """Automatically create a version when a milestone is completed. Sample plugin demonstrating the IMilestoneChangeListener interface. Creates a version from a just-completed milestone based on whether the milestone's name matches a specified pattern. """ implements(IMilestoneChangeListener) pattern = Option('milestone_to_version', 'pattern', r'(?i)(?:v(?:er)?\.?|version)?\s*(?P\d.*)', """A regular expression to match the names of milestones that should be made into versions when they are completed. The pattern must include one named group called 'version' that matches the version number itself.""") def milestone_created(self, milestone): pass def milestone_changed(self, milestone, old_values): if not milestone.is_completed or 'completed' not in old_values \ or old_values['completed'] is not None: return m = re.match(self.pattern, milestone.name) if not m: return version_name = m.groupdict().get('version') if not version_name: return try: version = Version(self.env, version_name) if not version.time: version.time = milestone.completed version.update() self.log.info('Existing version "%s" updated with completion ' 'time from milestone "%s"', version.name, milestone.name) else: self.log.info('Version "%s" already exists. No new version ' 'created from milestone "%s"', version.name, milestone.name) except ResourceNotFound: version = Version(self.env) version.name = version_name version.time = milestone.completed version.insert() self.log.info('New version "%s" created from completed milstone ' '"%s".', version.name, milestone.name) def milestone_deleted(self, milestone): pass trac-1.0.9+dfsg/sample-plugins/workflow/0000755000175000017500000000000012574541731016350 5ustar wmbwmbtrac-1.0.9+dfsg/sample-plugins/workflow/VoteOperation.py0000644000175000017500000000570012574541640021521 0ustar wmbwmb# -*- coding: utf-8 -*- # # Copyright (C) 2007-2013 Edgewall Software # Copyright (C) 2007 Eli Carter # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://trac.edgewall.org/. from genshi.builder import tag from trac.core import implements,Component from trac.ticket.api import ITicketActionController from trac.ticket.default_workflow import ConfigurableTicketWorkflow from trac.ticket.model import Priority, Ticket #from trac.perm import IPermissionRequestor # (TODO) revision = "$Rev: 12164 $" url = "$URL: http://svn.edgewall.org/repos/trac/branches/1.0-stable/sample-plugins/workflow/VoteOperation.py $" class VoteOperation(Component): """Provides a simplistic vote feature. This is a sample action controller illustrating how to create additional ''operations''. Don't forget to add `VoteOperation` to the workflow option in the `[ticket]` section in TracIni. If there is no other workflow option, the line will look like this: {{{ workflow = ConfigurableTicketWorkflow,VoteOperation }}} """ implements(ITicketActionController) def get_ticket_actions(self, req, ticket): controller = ConfigurableTicketWorkflow(self.env) return controller.get_actions_by_operation_for_req(req, ticket, 'vote') def get_all_status(self): return [] def render_ticket_action_control(self, req, ticket, action): id = 'vote_%s_result' % (action, ) selected_value = req.args.get(id, 'for') options = ['for', 'against'] return ("vote", tag.select([tag.option(x, selected=(x == selected_value or None)) for x in options], name=id, id=id), "Vote on the issue, raising or lowering its priority") def get_ticket_changes(self, req, ticket, action): id = 'vote_%s_result' % (action, ) selected = req.args.get(id, 'for') priorities = list(Priority.select(self.env)) orig_ticket = Ticket(self.env, ticket.id) current_priority = int(Priority(self.env, name= orig_ticket['priority']).value) if selected == 'for': # priorities are 1-based, not 0-based new_value = max(1, current_priority - 1) else: maxval = max([int(p.value) for p in priorities]) new_value = min(maxval, current_priority + 1) return {'priority': [p.name for p in priorities if int(p.value) == new_value][0]} def apply_action_side_effects(self, req, ticket, action): pass trac-1.0.9+dfsg/sample-plugins/workflow/enterprise-review-workflow.ini0000644000175000017500000000462012574541640024401 0ustar wmbwmb; enterprise-review-workflow.ini ; Add CodeReviewActionController to the workflow option in [ticket]. If there ; is no workflow option, the line will look like this: ; workflow = ConfigurableTicketWorkflow,CodeReviewActionController [ticket-workflow] ; assign, reassign, unassign actions assign = new -> assigned assign.operations = set_owner assign.permissions = TICKET_MODIFY reassign = assigned,in_work -> assigned reassign.operations = set_owner reassign.permissions = TICKET_MODIFY reassign_closed = closed -> closed reassign_closed.name = reassign reassign_closed.operations = set_owner reassign_closed.permissions = TICKET_MODIFY unassign = assigned,in_work -> new unassign.operations = del_owner unassign.permissions = TICKET_MODIFY ; leave actions leave = * -> * leave.operations = leave_status leave.default = 1 ; resolve actions resolve = in_QA -> closed resolve.operations = set_resolution resolve.permissions = TICKET_MODIFY fail = in_QA -> assigned fail.permissions = TICKET_MODIFY ; start/stop actions start = assigned -> in_work start.operations = set_owner_to_self start.permissions = TICKET_MODIFY stop = in_work -> assigned stop.permissions = TICKET_MODIFY ; reopen actions reopen = closed -> new reopen.operations = del_resolution reopen.permissions = TICKET_CREATE ; needinfo actions ; For tickets with an owner, they go back to assigned needinfo = assigned,in_work,in_QA -> needinfo needinfo.permissions = TICKET_MODIFY infoprovided = needinfo -> assigned infoprovided.permissions = TICKET_MODIFY infoprovided.default = 2 ; But tickets without an owner go back to new. needinfo_new = new -> needinfo_new needinfo_new.name = needinfo needinfo_new.permissions = TICKET_MODIFY infoprovided_new = needinfo_new -> new infoprovided_new.permissions = TICKET_MODIFY infoprovided_new.default = 2 ; review actions request_review = in_work -> in_review request_review.name = request review review = in_review -> * review.operations = code_review,hidden ; A reviewer can approve the work as-is, approve the work with a few things ; that need touched-up, but that won't need further review, or request changes ; significant enough that another review will be needed. review.code_review = approve -> in_QA, approve as noted -> post_review, request changes -> in_work review.permissions = TICKET_REVIEW submit_to_test = post_review -> in_QA submit_to_test.name = submit to test trac-1.0.9+dfsg/sample-plugins/workflow/DeleteTicket.py0000644000175000017500000000415512574541640021274 0ustar wmbwmb# -*- coding: utf-8 -*- # # Copyright (C) 2007-2013 Edgewall Software # Copyright (C) 2007 Eli Carter # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://trac.edgewall.org/. from trac.core import implements,Component from trac.ticket.api import ITicketActionController from trac.perm import IPermissionRequestor revision = "$Rev: 12731 $" url = "$URL: http://svn.edgewall.org/repos/trac/branches/1.0-stable/sample-plugins/workflow/DeleteTicket.py $" class DeleteTicketActionController(Component): """Provides the admin with a way to delete a ticket. Illustrates how to create an action controller with side-effects. Don't forget to add `DeleteTicketActionController` to the workflow option in the `[ticket]` section in TracIni. If there is no other workflow option, the line will look like this: {{{ workflow = ConfigurableTicketWorkflow,DeleteTicketActionController }}} """ implements(ITicketActionController, IPermissionRequestor) # IPermissionRequestor methods def get_permission_actions(self): return ['TICKET_DELETE'] # ITicketActionController methods def get_ticket_actions(self, req, ticket): actions = [] if 'TICKET_DELETE' in req.perm(ticket.resource): actions.append((0,'delete')) return actions def get_all_status(self): return [] def render_ticket_action_control(self, req, ticket, action): return ("delete ticket", '', "This ticket will be deleted.") def get_ticket_changes(self, req, ticket, action): return {} def apply_action_side_effects(self, req, ticket, action): # Be paranoid here, as this should only be called when # action is delete... if action == 'delete': ticket.delete() trac-1.0.9+dfsg/sample-plugins/workflow/CodeReview.py0000644000175000017500000001210612574541640020755 0ustar wmbwmb# -*- coding: utf-8 -*- # # Copyright (C) 2007-2013 Edgewall Software # Copyright (C) 2007 Eli Carter # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://trac.edgewall.org/. from genshi.builder import tag from trac.core import Component, implements from trac.perm import IPermissionRequestor from trac.ticket.api import ITicketActionController from trac.ticket.default_workflow import ConfigurableTicketWorkflow revision = "$Rev: 13230 $" url = "$URL: http://svn.edgewall.org/repos/trac/branches/1.0-stable/sample-plugins/workflow/CodeReview.py $" class CodeReviewActionController(Component): """Support for simple code reviews. The action that supports the `code_review` operation will present an extra choice for the review decision. Depending on that decision, a specific state will be selected. Example (from the enterprise-review-workflow.ini): {{{ review = in_review -> * review.name = review as review.operations = code_review review.code_review = approve -> in_QA, approve as noted -> post_review, request changes -> in_work }}} Don't forget to add the `CodeReviewActionController` to the workflow option in the `[ticket]` section in TracIni. If there is no other workflow option, the line will look like this: {{{ workflow = ConfigurableTicketWorkflow,CodeReviewActionController }}} """ implements(ITicketActionController, IPermissionRequestor) # IPermissionRequestor methods def get_permission_actions(self): return ['TICKET_REVIEW'] # ITicketActionController methods def get_ticket_actions(self, req, ticket): # The review action is available in those status where it has been # configured, for those users who have the TICKET_REVIEW permission, as # long as they are not the owner of the ticket (you can't review your # own work!). actions_we_handle = [] if req.authname != ticket['owner'] and \ 'TICKET_REVIEW' in req.perm(ticket.resource): controller = ConfigurableTicketWorkflow(self.env) actions_we_handle = controller.get_actions_by_operation_for_req( req, ticket, 'code_review') self.log.debug('code review handles actions: %r', actions_we_handle) return actions_we_handle def get_all_status(self): all_status = set() controller = ConfigurableTicketWorkflow(self.env) ouractions = controller.get_actions_by_operation('code_review') for weight, action in ouractions: status = [status for option, status in self._get_review_options(action)] all_status.update(status) return all_status def render_ticket_action_control(self, req, ticket, action): id, grade = self._get_grade(req, action) review_options = self._get_review_options(action) actions = ConfigurableTicketWorkflow(self.env).actions selected_value = grade or review_options[0][0] label = actions[action]['name'] control = tag(["as: ", tag.select([tag.option(option, selected= (option == selected_value or None)) for option, status in review_options], name=id, id=id)]) if grade: new_status = self._get_new_status(req, ticket, action, review_options) hint = "Next status will be '%s'" % new_status else: hint = "Next status will be one of " + \ ', '.join(["'%s'" % status for option, status in review_options]) return (label, control, hint) def get_ticket_changes(self, req, ticket, action): new_status = self._get_new_status(req, ticket, action) return {'status': new_status or 'new'} def apply_action_side_effects(self, req, ticket, action): pass # Internal methods def _get_grade(self, req, action): id = action + '_code_review_result' return id, req.args.get(id) def _get_review_options(self, action): return [[x.strip() for x in raw_option.split('->')] for raw_option in self.config.getlist('ticket-workflow', action + '.code_review')] def _get_new_status(self, req, ticket, action, review_options=None): id, grade = self._get_grade(req, action) if not review_options: review_options = self._get_review_options(action) for option, status in review_options: if grade == option: return status trac-1.0.9+dfsg/sample-plugins/workflow/MilestoneOperation.py0000644000175000017500000001227212574541640022545 0ustar wmbwmb# -*- coding: utf-8 -*- # # Copyright (C) 2002-2013 Edgewall Software # Copyright (C) 2012 Franz Mayer # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://trac.edgewall.org/. from genshi.builder import tag from trac.core import Component, implements from trac.resource import ResourceNotFound from trac.ticket.api import ITicketActionController from trac.ticket.default_workflow import ConfigurableTicketWorkflow from trac.ticket.model import Milestone from trac.util.translation import _ from trac.web.chrome import add_warning revision = "$Rev$" url = "$URL$" class MilestoneOperation(Component): """Sets milestone for specific status. === Example === {{{ [ticket-workflow] resolve.operations = set_resolution,set_milestone resolve.milestone = invalid,wontfix,duplicate,worksforme->rejected }}} When setting status to `duplicate` the milestone will automatically change to `rejected`. '''Note:''' if user has changed milestone manually, this workflow operation has ''no effect''! === Configuration === Don't forget to add `MilestoneOperation` to the workflow option in `[ticket]` section. If there is no workflow option, the line will look like this: {{{ [ticket] workflow = ConfigurableTicketWorkflow,MilestoneOperation }}} """ implements(ITicketActionController) def get_ticket_actions(self, req, ticket): actions_we_handle = [] if req.authname != 'anonymous' and \ 'TICKET_MODIFY' in req.perm(ticket.resource): controller = ConfigurableTicketWorkflow(self.env) actions_we_handle = controller.get_actions_by_operation_for_req( req, ticket, 'set_milestone') self.log.debug('set_milestone handles actions: %r', actions_we_handle) return actions_we_handle def get_all_status(self): return [] def render_ticket_action_control(self, req, ticket, action): actions = ConfigurableTicketWorkflow(self.env).actions label = actions[action]['name'] res_ms = self.__get_resolution_milestone_dict(ticket, action) resolutions = '' milestone = None for i, resolution in enumerate(res_ms): if i > 0: resolutions = "%s, '%s'" % (resolutions, resolution) else: resolutions = "'%s'" % resolution milestone = res_ms[resolution] hint = None if res_ms: try: Milestone(self.env, milestone) except ResourceNotFound: pass else: hint = _("For resolution %(resolutions)s the milestone will " "be set to '%(milestone)s'.", resolutions=resolutions, milestone=milestone) return (label, None, hint) def get_ticket_changes(self, req, ticket, action): if action == 'resolve' and \ req.args and 'action_resolve_resolve_resolution' in req.args: old_milestone = ticket._old.get('milestone') or None user_milestone = ticket['milestone'] or None # If there's no user defined milestone, we try to set it # using the defined resolution -> milestone mapping. if old_milestone is None: new_status = req.args['action_resolve_resolve_resolution'] new_milestone = self.__get_new_milestone(ticket, action, new_status) # ... but we don't reset it to None unless it was None if new_milestone is not None or user_milestone is None: try: milestone = Milestone(self.env, new_milestone) self.log.info('changed milestone from %s to %s', old_milestone, new_milestone) return {'milestone': new_milestone} except ResourceNotFound: add_warning(req, _("Milestone %(name)s does not exist.", name=new_milestone)) return {} def apply_action_side_effects(self, req, ticket, action): pass def __get_new_milestone(self, ticket, action, new_status): """Determines the new status""" if new_status: res_ms = self.__get_resolution_milestone_dict(ticket, action) return res_ms.get(new_status) def __get_resolution_milestone_dict(self, ticket, action): transitions = self.config.get('ticket-workflow', action + '.milestone').strip() transition = [x.strip() for x in transitions.split('->')] res_milestone = {} if len(transition) == 2: resolutions = [y.strip() for y in transition[0].split(',')] for res in resolutions: res_milestone[res] = transition[1] return res_milestone trac-1.0.9+dfsg/sample-plugins/workflow/StatusFixer.py0000644000175000017500000000571712574541640021214 0ustar wmbwmb# -*- coding: utf-8 -*- # # Copyright (C) 2007-2013 Edgewall Software # Copyright (C) 2007 Eli Carter # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://trac.edgewall.org/. from genshi.builder import tag from trac.core import Component, implements from trac.ticket.api import ITicketActionController, TicketSystem from trac.perm import IPermissionRequestor revision = "$Rev: 12164 $" url = "$URL: http://svn.edgewall.org/repos/trac/branches/1.0-stable/sample-plugins/workflow/StatusFixer.py $" class StatusFixerActionController(Component): """Provides the admin with a way to correct a ticket's status. This plugin is especially useful when you made changes to your workflow, and some ticket status are no longer valid. The tickets that are in those status can then be set to some valid state. Don't forget to add `StatusFixerActionController` to the workflow option in the `[ticket]` section in TracIni. If there is no other workflow option, the line will look like this: {{{ workflow = ConfigurableTicketWorkflow,StatusFixerActionController }}} """ implements(ITicketActionController, IPermissionRequestor) # IPermissionRequestor methods def get_permission_actions(self): return ['TICKET_STATUSFIX'] # ITicketActionController methods def get_ticket_actions(self, req, ticket): actions = [] if 'TICKET_STATUSFIX' in req.perm(ticket.resource): actions.append((0, 'force_status')) return actions def get_all_status(self): """Return all the status that are present in the database, so that queries for status no longer in use can be made. """ return [status for status, in self.env.db_query("SELECT DISTINCT status FROM ticket")] def render_ticket_action_control(self, req, ticket, action): # Need to use the list of all status so you can't manually set # something to an invalid state. selected_value = req.args.get('force_status_value', 'new') all_status = TicketSystem(self.env).get_all_status() render_control = tag.select( [tag.option(x, selected=(x == selected_value and 'selected' or None)) for x in all_status], id='force_status_value', name='force_status_value') return ("force status to:", render_control, "The next status will be the selected one") def get_ticket_changes(self, req, ticket, action): return {'status': req.args.get('force_status_value')} def apply_action_side_effects(self, req, ticket, action): pass trac-1.0.9+dfsg/sample-plugins/revision_links.py0000644000175000017500000000522412574541640020110 0ustar wmbwmb# -*- coding: utf-8 -*- # # Copyright (C) 2007-2013 Edgewall Software # Copyright (C) 2007 Christian Boos # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://trac.edgewall.org/. """Sample Wiki syntax extension plugin.""" from genshi.builder import tag from trac.core import * from trac.util.text import shorten_line from trac.versioncontrol.api import NoSuchChangeset, RepositoryManager from trac.versioncontrol.web_ui import ChangesetModule from trac.wiki.api import IWikiSyntaxProvider revision = "$Rev: 12500 $" url = "$URL: http://svn.edgewall.org/repos/trac/branches/1.0-stable/sample-plugins/revision_links.py $" class RevisionLinks(Component): """Adds a few more ways to refer to changesets.""" implements(IWikiSyntaxProvider) KEYWORDS = ['[Rr]ev(?:ision)?', '[Cc]hangeset'] # IWikiSyntaxProvider methods def get_wiki_syntax(self): def revlink(f, match, fullmatch): elts = match.split() rev = elts[1] # ignore keyword reponame = '' if len(elts) > 2: # reponame specified reponame = elts[-1] return self._format_revision_link(f, 'revision', reponame, rev, rev, fullmatch) yield (r"!?(?:%s)\s+%s(?:\s+in\s+\w+)?" % ("|".join(self.KEYWORDS), ChangesetModule.CHANGESET_ID), revlink) def get_link_resolvers(self): def resolverev(f, ns, rev, label, fullmatch): return self._format_revision_link(f, ns, '', rev, label, fullmatch) yield ('revision', resolverev) def _format_revision_link(self, formatter, ns, reponame, rev, label, fullmatch=None): rev, params, fragment = formatter.split_link(rev) try: repos = RepositoryManager(self.env).get_repository(reponame) if repos: changeset = repos.get_changeset(rev) return tag.a(label, class_="changeset", title=shorten_line(changeset.message), href=(formatter.href.changeset(rev) + params + fragment)) except NoSuchChangeset: pass return tag.a(label, class_="missing changeset", rel="nofollow", href=formatter.href.changeset(rev)) trac-1.0.9+dfsg/.gitignore0000644000175000017500000000036412574541640013530 0ustar wmbwmb*~ .*.sw[op] .svn .hg build dist Trac.egg-info *.pyc testenv testing.log functional-testing.log html htmlcov figleaf .coverage .figleaf* *.figleaf Makefile.cfg doc/.build *.mo trac/htdocs/js/messages/*.js .idea .project .pydevproject .settings trac-1.0.9+dfsg/tracini.cfg0000644000175000017500000000031212574541635013647 0ustar wmbwmb# mapping file for extracting messages of `*Option` and `ConfigSection` from # python files into trac/locale/tracini.pot (see setup.cfg) [python: **.py] [extractors] python = trac.dist:extract_python trac-1.0.9+dfsg/setup_wininst.bmp0000644000175000017500000011605612574541635015165 0ustar wmbwmbBM.6(  @@ "+B /$&$o*-+131&'c7:8./e>A?FHF<>c!!LNLGGiPRQUWV33\_]dgeHIknlknsutzvvx{y]\}~uu?????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????=0????????????????????????????????????????????????-=?????????????????????????????????????????????????????????????????????????????????????????????755:8?>;+75??=5=?:??:;??=557:?:?>;?75::>?:???>55;????;48?754:??87?==?:????:27??84=?;4:?:??;4:?>7:????>54>?=5;?:??=:?>=?84=?=5=?=4;??????????????????????*;=>/:2;*7=/?+>,>,??(>??;+==4=,??->/= =*?*???+8=+???=*>70>>2:75;-=/?0>??=+>/5:2>(?+>':->?+?/=-=75???+=:*?+>-;+??7'?>/>->+?*>-=,>,??????????????????????2:???*(?2:?,>+>>>2=?*????'??:558?,?-?2::'5???-=?*???75??(?'?>-28>=?+?87??=:?:/75?2;+?:278?*???+>>=???7??(?*>>>4:?472?+=-???+>>>?>*??????????????????????:0==?7$>:2?2;&55+:4?/>???(???-=-?->+?;4?'/???:0>,=??;/??0:+>?*:'5,8+>>-?????(>>,?:4/=?,=-?,>??/-5'?????28?&45+;0?0>/>,?+???$55+?/=???????????????????????&77?+=+> ;,=45;/? 755>??0:??-?"8,>->?,=-57???0/,*???(??845+;+?*;0;4,;-????/:??+;5777?,?$5:/;-=+;+????:-??70;-?$5,?=/58087754;->-;/??????????????????????*???:?=>;8;??87??;:7';??:4??;?;8:?=>?;==?:???+?>4???*>?=->:7>?>5:?=:7>???=-?;;?5:?>;?;?;;>:7>?;5=????*??8?:5>?;;:??:>=?78??87??:5>??????????????????????-:==????????????????8????+??=;???????????????45:>???:->07????????????????>*=+;???????????????????????,;7/????????????8??????????????????????????????????=778?????????????????????:??>;????????????????:7=????;5:??????????????????=5;?????????????????????????78?????????????:?????????????????????????????????????????????????????????????????????????;8458=??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????>- '=?????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????; >??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????> >?????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????7;??5 $?????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????/:????, ???????????????????????????????????????????????????????????????????????????????????????????????????/$ &:???????>-"->??******8????????????:>????;(  />???????5******=??????-*****,??????=* +7??4*****;:******>:******>??????????????+ 7????7 2=4???=& "5?>5??:"?????? '?????;8????&= ;0=0=????????????? 4??4"7??/"?-?=>???7 ?????("???-=0=0=????????????5(&=> 8?0-? + ??? 7???????  =0=0=????????????????/'>??+8=*& -57???;??????84?? ???>=0=0=????????????5????;-----7>????8'*???>-*?????4----->?0>??$??????5=0=0=???????????:(----------5 ?????08 ?????4------------=? /?? =?*/5::=0=0=???????????0*?????88 ?????=-7=7?5"+??$=0=0=???????????5&(?????78 ?????>->+?;7/???2=0=0=???????????=/?????-8?????;-& ?+?,& ?>8????>5'  =0=0=???????????? 7????;8=????8"=????*-0?????:;?>&??"?;;;;;;????>?0=0=???????????? ????"?5:?> 85=??7-=4???=-$???:??0? 0???-?0=0=????????????=  7??8?-?*"? *???4???>4  &?0=0=?????????????8 ???: 8?= -??$75;???? (????=??0=0=??????????????>* 4?????= 788??>,:*???:=??????$;????2=>$>??0=0=????????????????>2*" *2?????????5*'4>?88????7*$/=??------7?????:/'&-8???0-----7?????=------0??????-----;???2'  '2?????0=0=??????????????????????????????????????????88??????????????????????????????????????????????????????????????????????????????????????0=0=??????????????????????????????????????????88??????????????????????????????????????????????????????????????????????????????????????0=0=??????????????????????????????????????????88??????????????????????????????????????????????????????????????????????????????????????0=0=??????????????????????????????????????????88??????????????????????????????????????????????????????????????????????????????????????0=0=??????????????????????????????????????????88??????????????????????????????????????????????????????????????????????????????????????0=0=??????????????????????????????????????????::??????????????????????????????????????????????????????????????????????????????????????5>5>????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????=4?????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????5 =??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????$4???????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????;>????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????/ :?????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????> +???????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????7 =????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????( 5?????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????;"???????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????2 :???????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????> -?????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????8 =??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????+ 7???????????????????????????????????????????????????????????????????????????????????????????????????????????????????=&?????????????????????????????????????????????????????????????????????????????????????????????????????????????????5;??????????????????????????????????????????????????????????????????????????????????????????????????????????????$ /???????????????????????????????????????????????????????????????????????????????????????????????????????????;>????????????????????????????????????????????????????????????????????????????????????????????????????????/  7?????????????????????????????????????????????????????????????????????????????????????????????????????>'???????????????????????????????????????????????????????????????????????????????????????????????????7 ;????????????????????????????????????????????????????????????????????????????????????????????????* 2??????????????????????????????????????????????????????????????????????????????????????????????> ??????????????????????????????????????????????????????????????????????????????????????????????4 ;????????????????????????????????????????????????????????????????????????????????????????????? /????????????????????????????????????????????????????????????????????????????????????????????? ????????????????????????????????????????????????????????????????????????????????????????????= ????????????????????????????????????????????????????????????????????????????????????????????2 ;??????????????????????????????????????????????????????????????????????????????????????????? /??????????????????????????????????????????????????????????????????????????????????????????? ??????????????????????????????????????????????????????????????????????????????????????????= ??????????????????????????????????????????????????????????????????????????????????????????0 ;????????????????????????????????????????????????????????????????????????????????????????? -????????????????????????????????????????????????????????????????????????????????????????? ????????????????????????????????????????????????????????????????????????????????????????= ????????????????????????????????????????????????????????????????????????????????????????/ !4;???????????????????????????????????????????????????????????????????????????????????????(>???; -???????????????????????????????????????????????????????????????????????????????????????  ,????????=" ??????????????????????????????????????????????????????????????????????????????????????; 5?????????????*??????????????????????????????????????????????????????????????????????????????????????/  :?????????????????0:?????????????????????????????????????????????????????????????????????????????????????!;?????????????????????7 ,????????????????????????????????????????????????????????????????????????????????????? !>?????????????????????????; ????????????????????????????????????????????????????????????????????????????????????;,??????????????????????????????>$ ????????????????????????????????????????????????????????????????????????????????????-  2???????????????????????????????????+:??????????????????????????????????????????????????????????????????????????????????? 7???????????????????????????????????????4,???????????????????????????????????????????????????????????????????????????????????;???????????????????????????????????????????8 ??????????????????????????????????????????????????????????????????????????????????:!=??????????????????????=5???????????????????????; ??????????????????????????????????????????????????????????????????????????????????, (>??????????????????????4 8??????????????????????>$ :????????????????????????????????????????????????????????????????????????????????? /??????????????????????:$  *=??????????????????????++????????????????????????????????????????????????????????????????????????????????? 5?????????????????????=+ 7???20??????????????????????4????????????????????????????????????????????????????????????????????????????????:$/:??????????????????40????????=+ 8?????????????????>7, ????????????????????????????????????????????????????????????????????????????????> +7>?????????:$ *=?????????????:"*=?????????=5( &???????????????????????????????????????????????????????????????????????????????????>7,  (4=?>+ 8???????????????????42??;0& $/:????????????????????????????????????????????????????????????????????????????????????????????:0& 2????????????????????????=+ (4=??????????????????????????????????????????????????????????????????????????????????????????????????????=5* *=?????????????????????????????:$ ,7>?????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????  8==4 ;=8&? +==: 7>; -==:&? ? ? ? ? 4=>8?:>4  -==:&?  7>;,==: > 7= ?" 2? ?$>?55 ;'? ? ?  ? ?8*'=?0 8055 ;'? ??$> :' ?? ? ->? ?"=? ? ?  ?  :? ?>?"=? ? -> ? ? ? $;?8? ? ? ? ? ? ? (5?====> ? ? ? ? ? ? $;?8 ? ? ? ?* ?  ? "= ? ? ? ?  ;'? ? ? > ? "= ? ? ?* ? ? ? ? &? ? 08 ;+ ?+ 0?- /= ?==?; 70 54 ?0 :/ 08 ;+ ?+ ? ? &? ? ? ? ;=;" =?== ,;=;* ?":>:&;>8 ? /7 4==4 ?$;>4 ,;=;* ?,=*=?== ;=;" 8+ ? ? ? ? ? > 7= 4 ? +; 2 7=>5      ?===8                +===5    ? 48    ? ? -=>: -=>: $;=8(? 4=>8 ? ? />;*? 8=;" ? += ? ? ? 8>7'? ?? &> ? &> ?& 4? 8+ *> ? ? ? /? ;, (>? ??? ? :4 :? ? 0> 0> ??? ? ? ?& ?? ?=* ?? ?? $?? ';?8 (;?8 ???====>? ?';>>?? ?85 ?? ?? ???,?-??? ??????48????$??? *?? *? ? ?8475??-? *>81*>?57??,2=:5:? ?&;=;'';=;' ?"?7>>5??/=-/=>;&8>=& ?87 ? ?(;>: 8>:-? ?? ?"? ?"?$?===>?==="?"= ?&?+ ??-=&?':=>72==;+??0>;,??';>52==;+ ?4>;,?8>= ?2==;+ ?"?-?4+?8 ??? ?'>$8=77=,???0??4:477=, ? ?0?? ?77=, ?"?87>74? ??=+ ?;-&?'?*= ???( ??'>?*= ??( ?? ??*= ? ?? => ;& ??87 ??$?"? ??*;>>? ?"? ?"? ?*;>>?? ? ?"? ?"?'>4/ >04 ? ?5:"?$??#*>"? ?"?#?(>?$*>$?"? ? ??"*>$?$?47&:-7&="?"?78#?&?5: =0$? ??&->$?5;57: =2&?0"?&->$?"?5: =2&?05>;-?8,? $?$?:8'? ?"4=>=0&?"?0>>=*&?':>8 4=>=1'?4=02>>=(>?>>&?4=>=0'?-;>;' "?$8-?":/&?;0 ?''?$?'?'? ->/>>0:'? *>*8= (?'? (? $7  77 $?7 (?*> (? *>" *:>>8& (? (?  (?  (>" """"""""""""""""""""" " " """"""""""" """"    """""""""""""""""""""""""""""""""""""""""""""""""""""""""""""""#"""#""""""""""""##"#"#"#"#""""""""""##""""""""""""#"##"""# ""#"""#"""#"""""""""#"""#""#""#"""""##""#""""""#""""""""""$$##$#$$$$#$$$$$$$$$$#$#$$#$$$$$$###$##$$$$$$$$$$$$#$$#$$$#$##  "##$##$#$$$$$$#$#"$#$"$$$$$$##$"$#$$#$$"$$"#$"$$$$#$##$$$$$$$$$#$$$$$$$$$##$$$$$$$$$$#$#$$$$$$$#$$$#$$$"####$$$$$$$$$$"" #"#$$$$$$$$$$$$$#$$$#$$$$$$$$$$$$$$$""$$$$$$$$$##$$#$$$$#$"$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$"'8>>=0*;>>8$&?;8$? "$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&$$>5 :4*>'":= &? ??$? "&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&*?"'?;/ *?+'?28:,"? $&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''&&$ /? ?$ """ '?>+-;"? $&&''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''&"'7?8'?  " '?'? ?"$? $&'''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''((((''('('((''''''''(''''(''''''('''''''''(('''''''''''(&$(8??>4 (?  &?7582$?"#'''''''''''(''('''''(((''''''''''''''('''''''''''('''''''((((((((((((((((((((((((((((((((((((((((((((((((((((''''(((('''>=/& &?''??( -=&?"&'((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((*(((((((((((((((((((((((((((((((((((((((((((('''(((((',?&  "$;4 '?+(?0> "#?0? $'(((((((((((((((((((((((((((((((((((((((((((((((((((((((((*(*(**((*((*****(*(((***(****(*(*****((*(((*((*(*((('&&''(***((>5&#$5>&&->-"&:>#+?=5 "" 7;? &((*(**((((**((****(((**(**((*(****((*(**(*****(*****(*******************************************((((((((((((('&$$&'(****(+8>>>;-'''-;>>:*&,??*$" +?? "&'(***(((((((((((((((((***(((((****************************+++++*++*+*++*++++*+*++++*****((*****(('''''''''''''$" $&(****('''''''''''&&&&&&&&&&&$  $'(**(''''''''''''''''(((('''''(***+***+*++*****++++++*+**++++++++++++++++++++++++++++**('''****'&$$$$$$$$&&&&&=- $'***('&&&&''''&&$$$$$$$$$$$$4>>>8""$'(('&$""$$$$$&&&$$$$&''&$$$$&'(*+++++++++++++++++++++++++++++,+++++,+++++++++++++++++*(&&'(**(&$" "$$$2:$'***'&$""$&&&&$" """ ""(?+7: "&''&" # """" "$$$$" "&(*+++++++++++++++++++++++++,,,,,,,,,,,,,,,,,,,,,,,,,,++*'$$$(**'$"  ""$?$"&***'$  $&$"   (? $&&$   "" #  $(*,+,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,+(-?$$&((&0=>;0? '?'? *?4"&(**-? '? "$+? :>=$8>>;( ';>:1?&? "#&7>=0?:>= "8>>;( &:>;/?""'*+,,,,,,,,,,,,,,,,,,,,,,,,,-,,---,,--,------,--,,--,,,*-?" $''*?0"&8? '?'?  7;> &((*-? (? "+? '?";40>=7 ";?&? #,?*4?&?" ;40>;8 ";?#"(+,-------,,----,--,-,,,,,,---------------------------,*-?" "&&,?&$&,? '?'?"?*;/"&(**-? '? "*? '?&? &?' -?'? $*?/'?&?&? &?' -? $'+-------------------------////////////////-//-////-/--+-? "$&,?&&'-?$(?'?0=$4;&'(**-?"(? "*? (?*?>>>>?$+?$" *? (?""$&2=>>?'?*?>>>>?$+?$" *? $(+--//----///////-//////-/////////////////////////////-+/?" "$&-?((*/?&+? '? ;4'*?-(*++/?$*?$"$,?$*? '?($"-?&+?,$$/?$+?'$&''&$+?$*? '?($"-?&+?+$"/?"$(,-///////////////////////////////////////////////////-,/?>>>>-/?++,2?*,?"(?"*?+++:8+,,+/?&+?5'(8>',?$$';8**;:*+=:**=?(/?7(*-?0(5>',?$$';8**;:*+=:((;?$&*-/////////////////////////00000000000000000000000000//-0?&"$'(0?---4?,/?&+?&7:+--5?/--,0?'-?5=?=2+>?>>*-;>>:---0=>=7?-2?8>7,8>>>4+>?>>*-;>>:/--0=>=4?'(+-//00000000000000000000000101000000001110011000000001/-1?'&&'*+-/101/,1?(-?*(,-///011/-2?**,,-1//-4?-,-11////////////////////11//-4?-,,///11//////,0?*+-/1000000110000000000101000010010000001001000001001110014?+***+,/10001/4?,0?,,/10000000/4?-,/1000001;01/0100010000010000100010000002;0/110010100100/4?--/00011100010101000000110110222222222222222222222222222205?>>>>7/00222205?/4?0/00222222205?0/002222220002222222222222222222222222222200022222222222205?0/0022222222222222222222222222222222222222222222222222222220000000222222220000022222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222244444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444444222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222220000010010000000000000020100001000000002000002000000000000002002000020000200101000000000200100102010010000000000022000000000020000001000010100000001000000000000000000100010000000000000010000000000000000000000000000000000000000000001000000000000000000000000100000010000000110000000000110010000010000000000//////////////1////11////1//1//1//////////////////////////////1///////////////////1/////////////////1//1//1///////////////////////////////////////////////////////1///1///////////////////////////////////////////////////////////1///////////////1///1/1//////////////////////////////////////////////////////1////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////--------------------------------------------------------------------------------------------------------------------------------------------------------??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????<.).>??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????6.>????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????%9???????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????%.???????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????<)????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????9))6???).?????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????<6???%????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????3???<9??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????39???66?????????????????????????????????????????????????????????????????????????????????????????????????????????????????????<.????9?????????????????????0' $/=?55555558????????????5* "-;????=5555557?????????:- $+27????????????????????????3)????))???????????????????>'-???????????2 0??(??????;&  ;?????????????????????%)????)%6????????????????????-??????????+=?????*$????????????????????.????.%9?????????????????????&-?????????8 ???????????????????????8& ??8"/70  ??????????????????))???????.)????6??????????????4:??????????????8??????8 ?? 8?????:4?????????????????3?????????.%????<3?????????????-5???????????????>???????( ?>0???????'"?????????????????<%)9??????????????????????????-=????????????????/ ?* ??????????????????????????????<)???93??????936?????)????????????-=???????:?????????=& ?/??????????????????????????????<???63?????)%????3????????????-=???????4????????????>;8740---- ?: ????????>5555555;??????????????%=======????????'?? 4???????,/??????????????>???<????9????????????-=???????55??????8-??$>??????:??????????????????).???>)????6????????? 4?5,>????=;??> 8???;???????????????????&'????/ 4???????????????????.????)6????>3.9???????????-?'&5?0:?????("????????????????????%>????)?????.6????????????->*? 5??-5???????0&?????????????????????))????<????3.???????????-;-??- 5???:& ;?????????; ";??????????????????????39????3????%???????????----+-----:=-------;????8,$:??????8*"  '0>?????????????>4&  $->?????????????????????????%?????3????)%???????????????-=??????????????????????????????????????????????????????????????????????????????????????????????69?????3????.6???????????????-=???????????????????????????????????????????????????????????????????????????????????????????????.9??????3)????9????????????????-=????????????????????????????????????????????????????????????????????????????????????????????????6%)?????????????????????????????????????????????????????????????????????????????????????????????.???????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????36???????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????6???????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????9???????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????)????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????.>????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????<.>?????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????trac-1.0.9+dfsg/ChangeLog0000644000175000017500000000043412574541635013314 0ustar wmbwmbThe ChangeLog can be viewed: * On the web at http://trac.edgewall.org/wiki/TracChangeLog * In the source at trac/wiki/default-pages/TracChangeLog * By navigating to the TracChangeLog page after installing or upgrading Trac. You must upgrade the wiki to get the latest TracChangeLog. trac-1.0.9+dfsg/contrib/0000755000175000017500000000000012574541731013176 5ustar wmbwmbtrac-1.0.9+dfsg/contrib/l10n_diff_index.py0000644000175000017500000000571312574541640016506 0ustar wmbwmb# -*- coding: utf-8 -*- # # Copyright (C) 2013 Edgewall Software # Copyright (C) 2013 Christian Boos # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://trac.edgewall.org/. """ L10N tool which prepares an index of "interesting" changes found in a .diff. Skipped changes are: - the changes for which the msgid has changed - removal only of msgstr content Example workflow 1), review changes to the 'fr' translation before committing: make diff-fr | less Example workflow 2), force a pull of all changes from Transifex:: make update updateopts=-N tx pull -f make update updateopts=-N svn diff > tx.diff python l10n_diff_index.py tx.diff svn revert -R . And then use either:: emacs tx.diff.index --eval '(grep-mode)' or:: vim -c :cbuffer -c :copen tx.diff.index This makes it easier to go through the potentially interesting changes only, and apply the corresponding chunks if needed. """ from bisect import bisect_left import re interesting_changes_re = re.compile(r''' \n \s (?: msgid(?:_plural)?\s)? ".*" \n # ' ' msgid or "...", (?: [-\s] ".*" \n # ' ' or - "...", | - msgstr(?:\[\d+\])? \s ".*" \n # or the -msgstr )* (?: ( \+ msgstr(?:\[\d+\])? \s "[^"].*" ) \n # \1 is a non-empty +msgstr | [+\s] msgstr(?:\[\d+\])? \s ".*" \n # or after the msgstr, (?: [-\s] ".*" \n # optional ' ' or -"...", )* ( \+ "[^"].*" ) # \2 is a non-empty +"..." ) ''', re.MULTILINE | re.VERBOSE) def index_diffs(path, diffs): linenums = [] re.sub(r'\n', lambda m: linenums.append(m.start()), diffs) index = [] for m in interesting_changes_re.finditer(diffs): line = m.group(m.lastindex) if line.startswith(('+"Project-Id-Version:', '+"PO-Revision-Date:')): continue pos = m.start(m.lastindex) index.append((bisect_left(linenums, pos) + 1, line)) return index def write_index_for(path): with open(path, 'rb') as f: diffs = unicode(f.read(), 'utf-8') changes = index_diffs(path, diffs) if changes: index = path + '.index' with open(index, 'wb') as idx: for n, line in changes: print>>idx, (u"%s:%s: %s" % (path, n, line)).encode('utf-8') print "%s: %d changes indexed in %s" % (path, len(changes), index) else: print "%s: no interesting changes" % (path,) if __name__ == '__main__': import sys for path in sys.argv[1:]: write_index_for(path) trac-1.0.9+dfsg/contrib/htdigest.py0000755000175000017500000000605012574541640015366 0ustar wmbwmb#!/usr/bin/env python # -*- coding: utf-8 -*- # # Copyright (C) 2006-2013 Edgewall Software # Copyright (C) 2006 Matthew Good # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.org/wiki/TracLicense. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://trac.edgewall.org/log/. # # Author: Matthew Good import errno import fileinput from getpass import getpass from hashlib import md5 from optparse import OptionParser import sys def ask_pass(): pass1 = getpass('New password: ') pass2 = getpass('Re-type new password: ') if pass1 != pass2: print >>sys.stderr, "They don't match, sorry" sys.exit(1) return pass1 def get_digest(userprefix, password=None): if password == None: password = ask_pass() return make_digest(userprefix, password) def make_digest(userprefix, password): return userprefix + md5(userprefix + password).hexdigest() usage = "%prog [-c] [-b] passwordfile realm username" parser = OptionParser(usage=usage) parser.add_option('-c', action='store_true', dest='create', default=False, help='Create a new file') parser.add_option('-b', action='store_true', dest='batch', default=False, help='Batch mode, password on the commandline.') opts, args = parser.parse_args() try: if opts.batch: filename, realm, username, password = args else: filename, realm, username = args password = None except ValueError: parser.error('Wrong number of arguments') prefix = '%s:%s:' % (username, realm) if opts.create: try: f = open(filename, 'w') except EnvironmentError, e: if e.errno == errno.EACCES: print >>sys.stderr, 'Unable to update file', filename sys.exit(1) else: raise try: print >>f, get_digest(prefix, password) finally: f.close() else: try: matched = False for line in fileinput.input(filename, inplace=True): if line.startswith(prefix): if not matched: print get_digest(prefix, password) matched = True else: print line, if not matched: f = open(filename, 'a') try: print >>f, get_digest(prefix, password) finally: f.close() except EnvironmentError, e: if e.errno == errno.ENOENT: print >>sys.stderr, 'Could not open passwd file %s for reading.' \ % filename print >>sys.stderr, 'Use -c option to create a new one.' sys.exit(1) elif e.errno == errno.EACCES: print >>sys.stderr, 'Unable to update file', filename sys.exit(1) else: raise trac-1.0.9+dfsg/contrib/l10n_reset_en_GB.py0000644000175000017500000000416312574541640016561 0ustar wmbwmb# -*- coding: utf-8 -*- # # Copyright (C) 2013 Edgewall Software # Copyright (C) 2013 Christian Boos # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://trac.edgewall.org/. """ L10N tool which copies all msgid to the msgstr. This can be useful to verify the actual changes in the en_UK message catalogs. """ import re msgid_msgstr_re = re.compile(r''' ( # \1 "en_US" \r?\n msgid \s ".*" \r?\n (?: (?: msgid_plural \s )? ".*" \r?\n )* ) ( # \2 "en_GB" msgstr.* \s ".*" \r?\n (?: (?: msgstr.* \s )? ".*" \r?\n )* \r?\n ) ''', re.MULTILINE | re.VERBOSE) def reset_file(path): with open(path, 'rb+') as f: eol = '\r\n' content = f.read() if eol not in content: eol = '\n' def reset_msgstr(m): msgid, msgstr = m.groups() if '\nmsgid_plural' in msgid: msgstr = (msgid .replace(eol + 'msgid_plural', eol + 'msgstr[1]') .replace(eol + 'msgid', 'msgstr[0]')) else: msgstr = msgid.replace(eol + 'msgid', 'msgstr') return msgid + msgstr + eol sanitized, nsub = msgid_msgstr_re.subn(reset_msgstr, content) if nsub: print("reset %d messages to en_US in %s" % (nsub, path)) f.seek(0) f.write(sanitized) f.truncate() else: print("no messages found in %s" % (path,)) if __name__ == '__main__': import sys for path in sys.argv[1:]: reset_file(path) trac-1.0.9+dfsg/contrib/trac-svn-hook0000755000175000017500000001742312574541640015625 0ustar wmbwmb#!/bin/sh # -*- coding: utf-8 -*- # # Copyright (C) 2009-2013 Edgewall Software # Copyright (C) 2009 Christian Boos # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://trac.edgewall.org/. # # = trac-svn-hook = # # Purpose:: this script is meant to be called from the Subversion hooks # for notifying Trac when changesets are added or modified. # # Scope:: The http://trac.edgewall.org/wiki/TracRepositoryAdmin page # describes how to directly call the relevant trac-admin commands # from the Subversion hooks. In most cases this should be enough, # however this script should make troubleshooting easier and # has support for notifying multiple Trac environments. # # Usage:: copy this script to some central place, for example in your # TRAC_ENV or TRAC_PARENT_ENV folder # **Be sure to read the Configuration Notes section below first** # then fill in the variables listed below the Configuration section. # # For each Subversion repository $REPOS that has to be monitored by # your Trac environment(s), you need to modify the hooks in order to # call the present script: # # Add this to your `$REPOS/hooks/post-commit` script: # # /path/to/trac-svn-hook $REPOS $REV # # If you allow revision property editing in `$REPOS/hooks/pre-revprop-change`, # then you can let Trac know about modified changesets by adding the following # lines to the `$REPOS/hooks/post-revprop-change` script: # # if [ "$PROPNAME" = "svn:log" -o "$PROPNAME" = "svn:author" ]; then # /path/to/trac-svn-hook $REPOS $REV $USER $PROPNAME # fi # # See also http://svnbook.red-bean.com/en/1.5/svn.reposadmin.create.html#svn.reposadmin.create.hooks # # Platform:: Unix or Cygwin. # # On Windows, if you have Cygwin installed, you can also use this # script instead of the `trac-svn-hook.cmd`. # In your `post-commit.bat` and `post-revprop-change.bat` hooks, call # this script using: # # bash /path/to/trac-svn-hook "%1" "%2" "%3" "%4" # # ----------------------------------------------------------------------------- # # == Configuration # # Uncomment and adapt to your local setup: # # export TRAC_ENV=/path/to/trac-env:/path/to/another/trac-env # export PATH=/path/to/python/bin:$PATH # export LD_LIBRARY_PATH=/path/to/python/lib:$LD_LIBRARY_PATH # # ----------------------------------------------------------------------------- # # == Configuration Notes # # As a preliminary remark, you should be aware that Subversion usually # run the hooks in a very minimal environment. # This is why we have to be very explicit about where to find things. # # According to http://subversion.apache.org/faq.html#hook-debugging, # one useful method for getting the post-commit hook to work is to call # the hook manually from a shell, as the user(s) which will end up running # the hook (e.g. wwwrun, www-data, nobody). For example: # # env - $REPOS/hooks/post-commit $REPOS 1234 # # or: # # env - $REPOS/hooks/post-revprop-change $REPOS 1234 nobody svn:log # # # The environment variables that have to be set in this script are # TRAC_ENV, PATH and eventually LD_LIBRARY_PATH. # # TRAC_ENV:: the path(s) to the Trac environment(s) # # In case you need to maintain more than one environment in sync with # the repository (using a different scope or not), simply specify more # than one path, using the ":" path separator (or ";" if the script is # used on Windows with Cygwin's bash - in this case also don't forget to # enclose the list of paths in quotes, e.g. TRAC_ENV="path1;path2"). # # Note that if you have to maintain multiple repositories and multiple Trac # environments, and you have some non-trivial mapping between repositories # and Trac environments, then you can leave the TRAC_ENV setting out of # this file and put it directly in your post-commit and post-revprop-change # hooks, so that the changes to each repositories are notified to the # appropriate environments (don't forget to export TRAC_ENV in this case). # # PATH:: the folder containing the trac-admin script # # This folder is typically the same as your Python installation bin/ folder. # If this is /usr/bin, then you probably don't need to put it in the PATH. # # Note that if you're using a python program installed in a non-default # location (such as /usr/local or a virtual environment), then you need # to add it to the PATH as well. # # LD_LIBRARY_PATH:: folder(s) containing additional required libraries # # You may also need to setup the LD_LIBRARY_PATH accordingly. # The same goes for any custom dependency, such as SQLite libraries or # SVN libraries: make sure everything is reachable. # For example, if you get errors like "global name 'sqlite' is not defined" # or similar, then make sure the LD_LIBRARY_PATH contains the path to all # the required libraries (libsqlite3.so in the above example). # # # ----------------------------------------------------------------------------- # # == Examples # # === Minimal setup example === # # Python is installed in /usr/bin, Trac was easy_install'ed. # # {{{ # export TRAC_ENV=/srv/trac/the_trac_env # }}} # # # === Virtualenv setup example === # # Here we're using a Trac installation set up using virtualenv # (http://pypi.python.org/pypi/virtualenv). # # In this example, the virtualenv is located in # /packages/trac/branches/trac-multirepos # and is based off a custom Python installation (/opt/python-2.4.4). # We're also using a custom SQLite build (/opt/sqlite-3.3.8). # # Note that virtualenv's activate script doesn't seem to care # about LD_LIBRARY_PATH and the only other thing it does and that # we need here is to set the PATH, we can as well do that ourselves: # # We also want to notify two Trac instances: # # {{{ # export TRAC_ENV=/srv/trac/the_trac_env:/srv/trac/trac_other_trac_env # export PATH=/packages/trac/branches/trac-multirepos/bin:$PATH # export LD_LIBRARY_PATH=/opt/python-2.4.4/lib:/opt/sqlite-3.3.8/lib:$LD_LIBRARY_PATH # }}} # # # === Cygwin setup example === # # {{{ # export TRAC_ENV=C:/Workspace/local/trac/devel # export PYTHONPATH=C:/Workspace/src/trac/repos/multirepos # export PATH=/C/Dev/Python261/Scripts:$PATH # }}} # # ----------------------------------------------------------------------------- # # This is the script itself, you shouldn't need to modify this part. # -- Command line arguments (cf. usage) REPOS="$1" REV="$2" USER="$3" PROPNAME="$4" # -- Foolproofing if [ -z "$REPOS" -o -z "$REV" ]; then echo "Usage: $0 REPOS REV" exit 2 fi if ! python -V 2>/dev/null; then echo "python is not in the PATH ($PATH), check PATH and LD_LIBRARY_PATH." exit 2 fi if [ -z "$TRAC_ENV" ]; then echo "TRAC_ENV is not set." exit 2 fi # -- Feedback echo "----" if [ -z "$USER" -a -z "$PROPNAME" ]; then EVENT="added" echo "Changeset $REV was added in $REPOS" else EVENT="modified" echo "Changeset $REV was modified by $USER in $REPOS" fi # -- Call "trac-admin ... changeset ... $REPOS $REV" for each Trac environment ifs=$IFS IFS=: if [ -n "$BASH_VERSION" ]; then # we can use Bash syntax if [[ ${BASH_VERSINFO[5]} = *cygwin ]]; then IFS=";" fi fi for env in $TRAC_ENV; do if [ -r "$env/VERSION" ]; then log=$env/log/svn-hooks-`basename $REPOS`.log nohup sh <> $log 2>&1 & echo "Changeset $REV $EVENT" trac-admin $env changeset $EVENT $REPOS $REV && \ echo "OK" || echo "FAILED: see the Trac log" EOF else echo "$env doesn't seem to be a Trac environment, skipping..." fi done IFS=$ifs trac-1.0.9+dfsg/contrib/make_status.py0000644000175000017500000000466512574541640016102 0ustar wmbwmb#!/usr/bin/env python # -*- coding: utf-8 -*- # # Copyright (C) 2014-2015 Edgewall Software # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.org/wiki/TracLicense. import StringIO import warnings from trac.util.text import print_table, printout def _svn_version(): from svn import core version = (core.SVN_VER_MAJOR, core.SVN_VER_MINOR, core.SVN_VER_MICRO) return '%d.%d.%d' % version + core.SVN_VER_TAG PACKAGES = [ ("Python", 'sys.version'), ("Setuptools", 'setuptools.__version__'), ("Genshi", 'genshi.__version__'), ("Babel", 'babel.__version__'), ("sqlite3", 'sqlite3.version'), ("PySqlite", 'pysqlite2.dbapi2.version'), ("MySQLdb", 'MySQLdb.__version__'), ("Psycopg2", 'psycopg2.__version__'), ("SVN bindings", '__main__._svn_version()'), ("Mercurial", 'mercurial.util.version()'), ("Pygments", 'pygments.__version__'), ("Pytz", 'pytz.__version__'), ("ConfigObj", 'configobj.__version__'), ("Docutils", 'docutils.__version__'), ("Twill", 'twill.__version__'), ("LXML", 'lxml.etree.__version__'), ("coverage", 'coverage.__version__'), ("figleaf", 'figleaf.__version__'), ] def package_versions(packages, out=None): name_version_pairs = [] for name, accessor in packages: version = resolve_accessor(accessor) name_version_pairs.append((name, version)) print_table(name_version_pairs, ("Package", "Version"), ' : ', out) def resolve_accessor(accessor): try: module, attr = accessor.rsplit('.', 1) version = attr.replace('()', '') version = getattr(__import__(module, {}, {}, [version]), version) if attr.endswith('()'): version = version() return version except Exception: return "not installed" def shift(prefix, block): return '\n'.join(prefix + line for line in block.split('\n') if line) def print_status(): warnings.filterwarnings('ignore', '', DeprecationWarning) # Twill 0.9... buf = StringIO.StringIO() package_versions(PACKAGES, buf) printout(shift(' ', buf.getvalue())) if __name__ == '__main__': print_status() trac-1.0.9+dfsg/contrib/README0000644000175000017500000000050212574541640014052 0ustar wmbwmbThis directory contains useful contributed scripts and programs for Trac. Please note that these scripts are provided AS-IS are NOT tested to the same extent as the main sources. They are usually maintained by the original author, typically listed at the top of the source file. See ../THANKS for a list of contributors.trac-1.0.9+dfsg/contrib/rpm/0000755000175000017500000000000012574541731013774 5ustar wmbwmbtrac-1.0.9+dfsg/contrib/rpm/installscript0000644000175000017500000000017112574541640016610 0ustar wmbwmbpython setup.py install --optimize=1 --single-version-externally-managed --root=$RPM_BUILD_ROOT --record=INSTALLED_FILES trac-1.0.9+dfsg/contrib/rpm/makerpm0000755000175000017500000000025112574541640015353 0ustar wmbwmb#!/bin/bash # Run this script from the top of the Trac source tree. # Tested on Fedora Core 6 python setup.py bdist_rpm --install-script=`pwd`/contrib/rpm/installscript trac-1.0.9+dfsg/contrib/trac-svn-post-commit-hook.cmd0000644000175000017500000000630412574541640020631 0ustar wmbwmb@ECHO OFF :: :: Copyright (C) 2007-2013 Edgewall Software :: Copyright (C) 2007 Markus Tacker :: Copyright (C) 2007 Christian Boos :: All rights reserved. :: :: This software is licensed as described in the file COPYING, which :: you should have received as part of this distribution. The terms :: are also available at http://trac.edgewall.com/license.html. :: :: This software consists of voluntary contributions made by many :: individuals. For the exact contribution history, see the revision :: history and logs, available at http://trac.edgewall.org/. :: Trac post-commit-hook script for Windows :: :: Modified for the multirepos branch to use the `changeset` command. :: Usage: :: :: 1. Insert the following line in your REPOS/hooks/post-commit.bat script: :: :: call %~dp0\trac-post-commit-hook.cmd %1 %2 :: :: 2. Check the 'Modify paths' section below, be sure to set at least TRAC_ENV :: :: 3. Verify that the hook is working: :: :: - enable DEBUG level logging to a file and to the console :: (see TracLogging) :: :: - call the trac-post-commit-hook.cmd from a cmd.exe shell: :: :: trac-post-commit-hook.cmd 123 :: :: - call the post-commit.bat hook from a cmd.exe shell (check that :: no unwanted side-effects could be triggered when doing this...): :: :: post-commit.bat 123 :: :: - in each case, verify that you actually see the logging from Trac :: and in particular that you see something like (near the end): :: :: DEBUG: Event changeset_added on for revision 123 :: :: ---------------------------------------------------------- :: Modify paths here: :: -- this one *must* be set set TRAC_ENV= :: -- set if Python is not in the system path set PYTHON_PATH= :: -- set to the folder containing trac/ if installed in a non-standard location set TRAC_PATH= :: ---------------------------------------------------------- :: -- Do not execute hook if trac environment does not exist if not exist %TRAC_ENV% goto :EOF :: -- Determine trac-admin :: By default assume it's reachable from the PATH set TRAC_ADMIN=trac-admin.exe :: ... or take it from the Scripts folder of the specified Python installation if not %PYTHON_PATH%.==. set TRAC_ADMIN="%PYTHON_PATH%/Scripts/trac-admin.exe" :: ... or take it from the specified Trac source checkout if not %TRAC_PATH%.==. set TRAC_ADMIN=python.exe "%TRAC_PATH%/trac/admin/console.py" :: -- Setup the environment set PATH=%PYTHON_PATH%;%PATH% set PYTHONPATH=%TRAC_PATH%;%PYTHONPATH% :: -- Retrieve the information that Subversion gave to the hook set REPOS=%1 set REV=%2 :: Now we're about to call trac-admin's changeset added command. :: We have to call it like that: :: :: repository changeset added :: :: where can be the repository symbolic name or directly :: the repository directory, which we happen to have in %REPOS%. %TRAC_ADMIN% "%TRAC_ENV%" changeset added "%REPOS%" "%REV%" :: Based on either the symbolic name or the %REPOS% information, :: Trac will figure out which repository (or which scoped repositories) :: it has to synchronize. trac-1.0.9+dfsg/contrib/sourceforge2trac.py0000644000175000017500000006103712574541640017035 0ustar wmbwmb# -*- coding: utf-8 -*- # # Copyright (C) 2004-2013 Edgewall Software # Copyright (C) 2004 Dmitry Yusupov # Copyright (C) 2004 Mark Rowe # Copyright (C) 2010 Anatoly Techtonik # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://trac.edgewall.org/. """ Import a Sourceforge project's tracker items into a Trac database. Requires: Trac 1.0 from http://trac.edgewall.org/ Python 2.5 from http://www.python.org/ 1.0 clean-up by cboos **untested**, use at your own risks and send patches The Sourceforge tracker items can be exported from the 'Backup' page of the project admin section. Substitute XXXXX with project id: https://sourceforge.net/export/xml_export2.php?group_id=XXXXX $Id$ Uses Trac 0.11 DB format version 21 SourceForge XML Export format identified by the header: Works with all DB backends. Attachments are not downloaded, but inserted as links to SF tracker. Ticket Types, Priorities and Resolutions ---------------------------------------- Conversion kills default Trac ticket types: - defect 1 - enhancement 2 - task 3 and priorities: - blocker 1 - critical 2 - major 3 - minor 4 - trivial 5 and resolutions: - fixed 1 - invalid 2 - wontfix 3 - duplicate 4 - worksforme 5 Versions and Milestones ----------------------- Kills versions and milestones from existing Trac DB Mapping ------- tracker_name == ticket_type group_name == version category_name == component user nobody == anonymous Not implemented (feature:reason) -------------------------------- attachments:made as a comment with links to attachments stored on SF (type,id,filename,size,time,description,author,ipnr) ticket_custom:unknown (ticket,name,value) history:imported only for summary, priority. closed date and owner fields severities:no field in source data """ #: rename users from SF to Trac user_map = {"nobody":"anonymous"} complete_msg = """ Conversion complete. You may want to login into Trac to verify names for ticket owners. You may also want to rename ticket types and priorities to default. """ from __future__ import with_statement from xml.etree.ElementTree import ElementTree import time import sys import trac.env # --- utility class DBNotEmpty(Exception): def __str__(self): return "Will not modify database with existing tickets!" class FlatXML(object): """Flat XML is XML without element attributes. Also each element may contain other elements or text, but not both. This object mirrors XML structure into own properties for convenient access to tree elements, i.e. flat.trackers[2].groups[2].group_name Uses recursion. """ def __init__(self, el=None): """el is ElementTree element""" if el: self.merge(el) def merge(self, el): """merge supplied ElementTree element into current object""" for c in el: if len(c.getchildren()) == 0: if c.text != None and len(c.text.strip()) != 0: self.__setattr__(c.tag, c.text) else: self.__setattr__(c.tag, []) else: #if c.getchildren()[0].tag == c.tag[:-1]: # c is a set of elements self.__setattr__(c.tag, [FlatXML(x) for x in c.getchildren()]) def __str__(self): buf = "" for sub in self.__dict__: val = self.__dict__[sub] if type(val) != list: buf += "%s : %s\n" % (sub, val) else: for x in val: buf += "\n ".join(x.__str__().split("\n")) return buf def __repr__(self): buf = "" for sub in self.__dict__: val = self.__dict__[sub] if type(val) != list: buf += "<%s>%s\n" % (sub, val, sub) else: for x in val: buf += "\n ".join(x.__repr__().split("\n")) return buf # --- SF data model class Tracker(FlatXML): """ http://sourceforge.net/?group_id=175454&atid=873299 873299 Bugs Bug Tracking System All site users Yes Send to goblinhack@gmail.com 2592000 1209600 0 0 1 632324 v1.0 (example) 885178 Interface (example) nobody 1 Fixed 2 Invalid ... 1 Open 2 Closed 3 Deleted 4 Pending ... http://sourceforge.net/support/tracker.php?aid=2471428 2471428 2 100 100 100 sbluen nobody goblinhack 1230400444 1231087612 5 glitch with edge of level
The mini-laser that the future soldier carries is so powerful that it even lets me go outside the level. I stand at the top edge of the level and then shoot up, and then it gets me somewhere where I am not supposed to go.
0 2335316 goblinhack 1175610236
Logged In: YES user_id=1577972 Originator: NO does this happen every game or just once? you could send me the saved file and I'll try and load it - old versions harldy ever work with newer versions - need to add some kind of warnings on that tx
...
http://sourceforge.net/tracker/download.php?group_id=175454&atid=873299&file_id=289080&aid= 289080 your_most_recent_game.gz my saved game 112968 application/x-gzip 1218987770 sbluen ... 7304242 IP Artifact Created: 76.173.48.148 1230400444 sbluen ...
...
...
""" def __init__(self, e): self.merge(e) class ExportedProjectData(object): """Project data container as Python object. """ def __init__(self, f): """Data parsing""" self.trackers = [] #: tracker properties and data self.groups = [] #: groups [] self.priorities = [] #: priorities used self.resolutions = [] #: resolutions (index, name) self.tickets = [] #: all tickets self.statuses = [] #: status (idx, name) self.used_resolutions = {} #: id:name self.used_categories = {} #: id:name # id '100' means no category self.used_categories['100'] = None self.users = {} #: id:name root = ElementTree().parse(f) self.users = dict([(FlatXML(u).userid, FlatXML(u).username) for u in root.find('referenced_users')]) for tracker in root.find('trackers'): tr = Tracker(tracker) self.trackers.append(tr) # groups-versions for grp in tr.groups: # group ids are tracker-specific even if names match g = (grp.id, grp.group_name) if g not in self.groups: self.groups.append(g) # resolutions for res in tr.resolutions: r = (res.id, res.name) if r not in self.resolutions: self.resolutions.append(r) # statuses self.statuses = [(s.id, s.name) for s in tr.statuses] # tickets for tck in tr.tracker_items: if type(tck) == str: print repr(tck) self.tickets.append(tck) if int(tck.priority) not in self.priorities: self.priorities.append(int(tck.priority)) res_id = getattr(tck, "resolution_id", None) if res_id is not None and res_id not in self.used_resolutions: for idx, name in self.resolutions: if idx == res_id: break self.used_resolutions[res_id] = \ dict(self.resolutions)[res_id] # used categories categories = dict(self.get_categories(tr, noowner=True)) if tck.category_id not in self.used_categories: self.used_categories[tck.category_id] = \ categories[tck.category_id] # sorting everything self.trackers.sort(key=lambda x:x.name) self.groups.sort() self.priorities.sort() def get_categories(self, tracker=None, noid=False, noowner=False): """ SF categories : Trac components (id, name, owner) tuples for specified tracker or all trackers if noid or noowner flags are set, specified tuple attribute is stripped """ trs = [tracker] if tracker is not None else self.trackers categories = [] for tr in trs: for cat in tr.categories: c = (cat.id, cat.category_name, cat.auto_assignee) if c not in categories: categories.append(c) #: sort by name if noid: categories.sort() else: categories.sort(key=lambda x:x[1]) if noowner: categories = [x[:2] for x in categories] if noid: categories = [x[1:] for x in categories] return categories class TracDatabase(object): def __init__(self, path): self.env = trac.env.Environment(path) def hasTickets(self): return int(self.env.db_query("SELECT count(*) FROM ticket")[0][0]) > 0 def dbCheck(self): if self.hasTickets(): raise DBNotEmpty def setTypeList(self, s): """Remove all types, set them to `s`""" self.dbCheck() with self.env.db_transaction as db: db("DELETE FROM enum WHERE type='ticket_type'") for i, value in enumerate(s): db("INSERT INTO enum (type, name, value) VALUES (%s, %s, %s)", ("ticket_type", value, i)) def setPriorityList(self, s): """Remove all priorities, set them to `s`""" self.dbCheck() with self.env.db_transaction as db: db("DELETE FROM enum WHERE type='priority'") for i, value in enumerate(s): db("INSERT INTO enum (type, name, value) VALUES (%s, %s, %s)", ("priority", value, i)) def setResolutionList(self, t): """Remove all resolutions, set them to `t` (index, name)""" self.dbCheck() with self.env.db_transaction as db: db("DELETE FROM enum WHERE type='resolution'") for value, name in t: db("INSERT INTO enum (type, name, value) VALUES (%s, %s, %s)", ("resolution", name, value)) def setComponentList(self, t): """Remove all components, set them to `t` (name, owner)""" self.dbCheck() with self.env.db_transaction as db: db("DELETE FROM component") for name, owner in t: db("INSERT INTO component (name, owner) VALUES (%s, %s)", (name, owner)) def setVersionList(self, v): """Remove all versions, set them to `v`""" self.dbCheck() with self.env.db_transaction as db: db("DELETE FROM version") for value in v: # time and description are also available db("INSERT INTO version (name) VALUES (%s)", value) def setMilestoneList(self, m): """Remove all milestones, set them to `m` (""" self.dbCheck() with self.env.db_transaction as db: db("DELETE FROM milestone") for value in m: # due, completed, description are also available db("INSERT INTO milestone (name) VALUES (%s)", value) def addTicket(self, type, time, changetime, component, priority, owner, reporter, cc, version, milestone, status, resolution, summary, description, keywords): """ ticket table db21.py format id integer PRIMARY KEY, type text, -- the nature of the ticket time integer, -- the time it was created changetime integer, component text, severity text, priority text, owner text, -- who is this ticket assigned to reporter text, cc text, -- email addresses to notify version text, -- milestone text, -- status text, resolution text, summary text, -- one-line summary description text, -- problem description (long) keywords text """ if status.lower() == 'open': if owner != '': status = 'assigned' else: status = 'new' with self.env.db_transaction as db: c = db.cursor() c.execute(""" INSERT INTO ticket (type, time, changetime, component, priority, owner, reporter, cc, version, milestone, status, resolution, summary, description, keywords) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) """, (type, time, changetime, component, priority, owner, reporter, cc, version, milestone, status.lower(), resolution, summary, '%s' % description, keywords)) return db.get_last_id(c, 'ticket') def addTicketComment(self, ticket, time, author, value): with self.env.db_transaction as db: db(""" INSERT INTO ticket_change (ticket, time, author, field, oldvalue, newvalue) VALUES (%s, %s, %s, %s, %s, %s) """, (ticket, time, author, 'comment', '', '%s' % value)) def addTicketChange(self, ticket, time, author, field, oldvalue, newvalue): with self.env.db_transaction as db: db("""INSERT INTO ticket_change (ticket, time, author, field, oldvalue, newvalue) VALUES (%s, %s, %s, %s, %s, %s) """, (ticket, time, author, field, oldvalue, newvalue)) def importData(f, env, opt): project = ExportedProjectData(f) trackers = project.trackers trac = TracDatabase(env) # Data conversion typeList = [x.name for x in trackers] print("%d trackers will be converted to the following ticket types:\n %s" \ % (len(trackers), typeList)) used_cat_names = set(project.used_categories.values()) #: make names unique, forget about competing owners (the last one wins) components = dict(project.get_categories(noid=True)).items() components.sort() components = [x for x in components if x[0] in used_cat_names] print "%d out of %d categories are used and will be converted to the following components:\n %s" \ % (len(components), len(project.get_categories()), components) print "..renaming component owners:" for i,c in enumerate(components): if c[1] in user_map: components[i] = (c[0], user_map[c[1]]) print " %s" % components print "%d groups which will be converted to the following versions:\n %s" \ % (len(project.groups), project.groups) print "%d resolutions found :\n %s" \ % (len(project.resolutions), project.resolutions) resolutions = [(k,project.used_resolutions[k]) for k in project.used_resolutions] resolutions.sort(key=lambda x:int(x[0])) print ".. only %d used will be imported:\n %s" \ % (len(resolutions), resolutions) print "Priorities used so far: %s" % project.priorities if not(raw_input("Continue [y/N]?").lower() == 'y'): sys.exit() # Data save trac.setTypeList(typeList) trac.setComponentList(components) trac.setPriorityList(range(min(project.priorities), max(project.priorities))) trac.setVersionList(set([x[1] for x in project.groups])) trac.setResolutionList(resolutions) trac.setMilestoneList([]) for tracker in project.trackers: # id 100 means no component selected component_lookup = dict(project.get_categories(noowner=True) + [("100", None)]) for t in tracker.tracker_items: i = trac.addTicket(type=tracker.name, time=int(t.submit_date), changetime=int(t.submit_date), component=component_lookup[t.category_id], priority=t.priority, owner=t.assignee \ if t.assignee not in user_map \ else user_map[t.assignee], reporter=t.submitter \ if t.submitter not in user_map \ else user_map[t.submitter], cc=None, # 100 means no group selected version=dict(project.groups + [("100", None)])[t.group_id], milestone=None, status=dict(project.statuses)[t.status_id], resolution=dict(resolutions)[t.resolution_id] \ if hasattr(t, "resolution_id") else None, summary=t.summary, description=t.details, keywords='sf' + t.id) print 'Imported %s as #%d' % (t.id, i) if len(t.attachments): attmsg = "SourceForge attachments:\n" for a in t.attachments: attmsg = attmsg + " * [%s %s] (%s) - added by '%s' %s [[BR]] "\ % (a.url+t.id, a.filename, a.filesize+" bytes", user_map.get(a.submitter, a.submitter), time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(int(a.date)))) attmsg = attmsg + "''%s ''\n" % (a.description or '') # empty description is as empty list trac.addTicketComment(ticket=i, time=time.strftime("%Y-%m-%d %H:%M:%S", time.localtime(int(t.submit_date))), author=None, value=attmsg) print ' added information about %d attachments for #%d' % \ (len(t.attachments), i) for msg in t.followups: """ 3280792 goblinhack 1231087739
done
""" trac.addTicketComment(ticket=i, time=msg.date, author=msg.submitter, value=msg.details) if t.followups: print ' imported %d messages for #%d' % (len(t.followups), i) # Import history """ 4452195 resolution_id 100 1176043865 goblinhack """ revision = t.__dict__.copy() # iterate the history in reverse order and update ticket revision from # current (last) to initial changes = 0 for h in sorted(t.history_entries, reverse=True): """ Processed fields (field - notes): IP - no target field, just skip summary priority close_date assigned_to Fields not processed (field: explanation): File Added - TODO resolution_id - need to update used_resolutions status_id artifact_group_id category_id group_id """ f = None if h.field_name in ("IP",): changes += 1 continue elif h.field_name in ("summary", "priority"): f = h.field_name oldvalue = h.old_value newvalue = revision.get(h.field_name, None) elif h.field_name == 'assigned_to': f = "owner" newvalue = revision['assignee'] if h.old_value == '100': # was not assigned revision['assignee'] = None oldvalue = None else: username = project.users[h.old_value] if username in user_map: username = user_map[username] revision['assignee'] = oldvalue = username elif h.field_name == 'close_date' and revision['close_date'] != 0: f = 'status' oldvalue = 'assigned' newvalue = 'closed' if f: changes += 1 trac.addTicketChange(ticket=i, time=h.date, author=h.updator, field=f, oldvalue=oldvalue, newvalue=newvalue) if h.field_name != 'assigned_to': revision[h.field_name] = h.old_value if changes: print ' processed %d out of %d history items for #%d' % \ (changes, len(t.history_entries), i) def main(): import optparse p = optparse.OptionParser( "Usage: %prog xml_export.xml /path/to/trac/environment") opt, args = p.parse_args() if len(args) != 2: p.error("Incorrect number of arguments") try: importData(open(args[0]), args[1], opt) except DBNotEmpty, e: print 'Error:', e sys.exit(1) print complete_msg if __name__ == '__main__': main() trac-1.0.9+dfsg/contrib/workflow/0000755000175000017500000000000012574541731015050 5ustar wmbwmbtrac-1.0.9+dfsg/contrib/workflow/workflow_parser.py0000755000175000017500000001103212574541640020647 0ustar wmbwmb#!/usr/bin/env python # -*- coding: utf-8 -*- # # Copyright (C) 2007-2013 Edgewall Software # Copyright (C) 2007 Eli Carter # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://trac.edgewall.org/. import sys import getopt import locale import pkg_resources pkg_resources.require('Trac') from trac.config import Configuration from trac.ticket.default_workflow import parse_workflow_config _debug = False def debug(s): if _debug: sys.stderr.write(s) def readconfig(filename): """Returns a list of raw config options""" config = Configuration(filename) rawactions = list(config.options('ticket-workflow')) debug("%s\n" % str(rawactions)) if not rawactions: sys.stderr.write("ERROR: You don't seem to have a [ticket-workflow] " "section.\n") sys.exit(1) return rawactions class ColorScheme(object): # cyan, yellow are too light in color colors = ['black', 'blue', 'red', 'green', 'purple', 'orange', 'darkgreen'] def __init__(self): self.mapping = {} self.coloruse = [0] * len(self.colors) def get_color(self, name): try: colornum = self.mapping[name] except KeyError: self.mapping[name] = colornum = self.pick_color(name) self.coloruse[colornum] += 1 return self.colors[colornum] def pick_color(self, name): """Pick a color that has not been used much so far.""" return self.coloruse.index(min(self.coloruse)) def actions2graphviz(actions, show_ops=False, show_perms=False): """Returns a list of lines to be fed to graphviz.""" # The size value makes it easier to create a useful printout. color_scheme = ColorScheme() digraph_lines = [""" digraph G { center=1 size="10,8" { rank=source; new [ shape=invtrapezium ] } { rank=sink; closed [ shape=trapezium ] } """] for action, attributes in actions.items(): label = [attributes['name'], ] if show_ops: label += attributes['operations'] if show_perms: label += attributes['permissions'] if 'set_resolution' in attributes: label += ['(' + attributes['set_resolution'] + ')'] for oldstate in attributes['oldstates']: color = color_scheme.get_color(attributes['name']) digraph_lines.append( ' "%s" -> "%s" [label="%s" color=%s fontcolor=%s]' % (oldstate, attributes['newstate'], '\\n'.join(label), color, color)) digraph_lines.append('}') return digraph_lines def main(filename, output, show_ops=False, show_perms=False): # Read in the config rawactions = readconfig(filename) # Parse the config information actions = parse_workflow_config(rawactions) # Convert to graphviz digraph_lines = actions2graphviz(actions, show_ops, show_perms) # And output output.write(unicode.encode('\n'.join(digraph_lines), locale.getpreferredencoding())) def usage(output): output.write('workflow_parser [options] configfile.ini [output.dot]\n' '-h --help shows this message\n' '-o --operations include operations in the graph\n' '-p --permissions include permissions in the graph\n') if __name__ == '__main__': show_ops = False show_perms = False try: opts, args = getopt.getopt(sys.argv[1:], 'hop', ['help', 'operations', 'permissions']) except getopt.GetoptError: usage(sys.stderr) sys.exit(1) for option, argument in opts: if option in ('-h', '--help'): usage(sys.stdout) sys.exit(0) elif option in ('-o', '--operations'): show_ops = True elif option in ('-p', '--permissions'): show_perms = True if not args: sys.stderr.write('Syntax error: config filename required.\n') usage(sys.stderr) sys.stderr.flush() sys.exit(1) ini_filename = args[0] if len(args) > 1: output = open(args[1], 'w') else: output = sys.stdout main(ini_filename, output, show_ops, show_perms) trac-1.0.9+dfsg/contrib/workflow/enterprise-workflow.ini0000644000175000017500000000344412574541640021605 0ustar wmbwmb[ticket-workflow] ; enterprise-workflow.ini ; assign, reassign, unassign actions assign = new -> assigned assign.operations = set_owner assign.permissions = TICKET_MODIFY reassign = assigned,in_work -> assigned reassign.operations = set_owner reassign.permissions = TICKET_MODIFY reassign_closed = closed -> closed reassign_closed.name = reassign reassign_closed.operations = set_owner reassign_closed.permissions = TICKET_MODIFY unassign = assigned,in_work -> new unassign.operations = del_owner unassign.permissions = TICKET_MODIFY ; leave actions leave = * -> * leave.operations = leave_status leave.default = 1 ; test actions test = new,assigned,in_work -> in_QA test.permissions = TICKET_MODIFY ; resolve actions resolve = in_QA -> closed resolve.operations = set_resolution resolve.permissions = TICKET_MODIFY fail = in_QA -> assigned fail.permissions = TICKET_MODIFY ; start/stop actions start = assigned -> in_work start.operations = set_owner_to_self start.permissions = TICKET_MODIFY stop = in_work -> assigned stop.permissions = TICKET_MODIFY ; reopen actions reopen = closed -> new reopen.operations = del_resolution reopen.permissions = TICKET_CREATE ; request info actions ; For tickets with an owner, they go back to assigned requestinfo = assigned,in_work,in_QA -> infoneeded requestinfo.name = request info requestinfo.permissions = TICKET_MODIFY provideinfo = infoneeded -> assigned provideinfo.name = provide info provideinfo.permissions = TICKET_MODIFY provideinfo.default = 2 ; But tickets without an owner go back to new. requestinfo_new = new -> infoneeded_new requestinfo_new.name = request info requestinfo_new.permissions = TICKET_MODIFY provideinfo_new = infoneeded_new -> new provideinfo_new.name = provide info provideinfo_new.permissions = TICKET_MODIFY provideinfo_new.default = 2 trac-1.0.9+dfsg/contrib/workflow/showworkflow0000755000175000017500000000265112574541640017554 0ustar wmbwmb#!/bin/bash -x # -*- coding: utf-8 -*- # # Copyright (C) 2007-2013 Edgewall Software # Copyright (C) 2007 Eli Carter # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://trac.edgewall.org/. basedir=`dirname $0` options="" while [ $# -gt 1 ]; do options="$options $1" shift done config="$1" if [ ! -e "$config" ]; then echo "Syntax error: requires a .ini file to work with." >&2 exit 1 fi dot=`echo "$config" | sed 's/\.ini$/.dot/g'` ps=`echo "$config" | sed 's/\.ini$/.ps/g'` pdf=`echo "$config" | sed 's/\.ini$/.pdf/g'` png=`echo "$config" | sed 's/\.ini$/.png/g'` $basedir/workflow_parser.py $options "$config" "$dot" if [ $? -ne 0 ]; then echo "Failed to parse \"$config\", exiting." >&2 exit 1 fi cat "$dot" if [ "$OSTYPE" = cygwin ]; then dot -T png -o "$png" "$dot" cmd /c start $png else dot -T ps -o "$ps" "$dot" dot -T pdf -o "$pdf" "$dot" # attempt to find a Linux pdf viewer for viewer in kpdf okular evince; do if which $viewer >/dev/null 2>&1; then break fi done $viewer "$pdf" fi trac-1.0.9+dfsg/contrib/workflow/README0000644000175000017500000000030412574541640015724 0ustar wmbwmbThere are two more workflows available, original-workflow and basic-workflow. Look in trac/ticket/workflows for them. (They are used internally by Trac, so they aren't here in the contrib tree.) trac-1.0.9+dfsg/contrib/workflow/simple-workflow.ini0000644000175000017500000000135112574541640020711 0ustar wmbwmb[ticket-workflow] ; simple-workflow.ini ; This is action-centric leave = * -> * leave.operations = leave_status leave.default = 1 resolve_new = new -> closed resolve_new.name = resolve resolve_new.permissions = TICKET_MODIFY resolve_new.operations = set_owner_to_self,set_resolution accept = new,accepted -> accepted accept.permissions = TICKET_MODIFY accept.operations = set_owner_to_self resolve_accepted = accepted -> closed resolve_accepted.name = resolve resolve_accepted.permissions = TICKET_MODIFY resolve_accepted.operations = set_resolution unaccept = accepted -> new unaccept.permissions = TICKET_MODIFY unaccept.operations = del_owner reopen = closed -> new reopen.permissions = TICKET_CREATE reopen.operations = del_resolution trac-1.0.9+dfsg/contrib/workflow/opensource-workflow.ini0000644000175000017500000000376412574541640021614 0ustar wmbwmb[ticket-workflow] ; opensource-workflow.ini ; accept action ; When you accept a ticket, you get ownership of it. (You can't accept a ; ticket on someone else's behalf.) accept = new,assigned,accepted,started -> accepted accept.operations = set_owner_to_self accept.permissions = TICKET_MODIFY ; assign, reassign, unassign actions assign = new -> assigned assign.operations = set_owner assign.permissions = TICKET_MODIFY reassign = assigned,accepted,started -> assigned reassign.operations = set_owner reassign.permissions = TICKET_MODIFY ; Allow correcting the ownership of a closed ticket. change_owner = closed -> closed change_owner.name = change ownership change_owner.operations = set_owner change_owner.permissions = TICKET_MODIFY unassign = assigned,accepted,started -> new unassign.operations = del_owner unassign.permissions = TICKET_MODIFY ; leave actions leave = * -> * leave.operations = leave_status leave.default = 1 ; resolve actions resolve = new,assigned,accepted,started -> closed resolve.operations = set_resolution resolve.permissions = TICKET_MODIFY ; start/stop actions start = accepted,assigned -> started start.operations = set_owner_to_self start.permissions = TICKET_MODIFY stop = started -> assigned stop.permissions = TICKET_MODIFY ; reopen actions reopen = closed -> new reopen.operations = del_resolution reopen.permissions = TICKET_CREATE ; request info actions ; For tickets with an owner, they go back to assigned requestinfo = assigned,accepted,started -> infoneeded requestinfo.name = request info requestinfo.permissions = TICKET_MODIFY provideinfo = infoneeded -> assigned provideinfo.name = provide info provideinfo.permissions = TICKET_MODIFY provideinfo.default = 2 ; But tickets without an owner go back to new. requestinfo_new = new -> infoneeded_new requestinfo_new.name = request info requestinfo_new.permissions = TICKET_MODIFY provideinfo_new = infoneeded_new -> new provideinfo_new.name = provide info provideinfo_new.permissions = TICKET_MODIFY provideinfo_new.default = 2 trac-1.0.9+dfsg/contrib/workflow/migrate_original_to_basic.py0000755000175000017500000000265712574541640022615 0ustar wmbwmb#!/usr/bin/python # -*- coding: utf-8 -*- # # Copyright (C) 2007-2013 Edgewall Software # Copyright (C) 2007 Eli Carter # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://trac.edgewall.org/. import sys import trac.env from trac.ticket.default_workflow import load_workflow_config_snippet def main(): """Rewrite the ticket-workflow section of the config; and change all 'assigned' tickets to 'accepted'. """ if len(sys.argv) != 2: print "Usage: %s path_to_trac_environment" % sys.argv[0] sys.exit(1) tracdir = sys.argv[1] trac_env = trac.env.open_environment(tracdir) # Update the config... old_workflow = trac_env.config.options('ticket-workflow') for name, value in old_workflow: trac_env.config.remove('ticket-workflow', name) load_workflow_config_snippet(trac_env.config, 'basic-workflow.ini') trac_env.config.save() # Update the ticket statuses... trac_env.db_transaction(""" UPDATE ticket SET status = 'accepted' WHERE status = 'assigned' """) if __name__ == '__main__': main() trac-1.0.9+dfsg/contrib/workflow/trivial-workflow.ini0000644000175000017500000000045212574541640021073 0ustar wmbwmb[ticket-workflow] ; trivial-workflow.ini resolve = new -> closed resolve.permissions = TICKET_MODIFY resolve.operations = set_resolution reopen = closed -> new reopen.permissions = TICKET_CREATE reopen.operations = del_resolution leave = * -> * leave.operations = leave_status leave.default = 1 trac-1.0.9+dfsg/contrib/wiki2rst.py0000755000175000017500000000711312574541640015332 0ustar wmbwmb#!/usr/bin/env python # -*- coding: utf-8 -*- import codecs import os.path import re import sys from cStringIO import StringIO from pkg_resources import resource_listdir, resource_string import html2rest from trac.loader import load_components from trac.test import EnvironmentStub, Mock, MockPerm from trac.util.text import printout from trac.web.chrome import web_context from trac.web.href import Href from trac.wiki.formatter import format_to_html from trac.wiki.model import WikiPage class Parser(html2rest.Parser): def __init__(self, writer=sys.stdout, encoding='utf8', relroot=None, relpath=None): html2rest.Parser.__init__(self, writer, encoding, relroot, relpath) self.links = {} def end_a(self): if '#pending' in self.hrefs: href = self.hrefs['#pending'] label = self.hrefs[href] key = label.lower() if key not in self.links: self.links[key] = (label, href) elif href != self.links[key][1]: alt = label while True: alt += '*' if alt not in self.links: break continue self.data(alt[len(label):]) self.hrefs[href] = alt self.links[alt] = (alt, href) self.data('`_') del self.hrefs['#pending'] def end_body(self): self.end_p() for label, href in self.links.itervalues(): if href[0] != '#': self.writeline('.. _%s: %s' % (label, href)) self.end_p() def wiki2rest(env, context, wiki): text = re.sub('\r?\n', '\n', wiki.text) text = re.sub(r'\[\[TracGuideToc\]\]\r?\n?', '', text) html = format_to_html(env, context, text) html = html.replace(u'\u200b', '') html = re.sub(r'\s*([^<]*?)\s*', r'\1', html) html = '%s' % html writer = StringIO() parser = Parser(writer, 'utf-8', None, None) parser.feed(html) parser.close() rst = writer.getvalue().strip('\n') rst = re.sub('\n{4,}', '\n\n\n', rst) # sort links rst = re.sub(r'(?:\n\.\. _[^\n]*)+\Z', lambda m: '\n'.join(sorted(m.group(0).split('\n'), key=lambda v: v.lower())), rst) if any(ord(c) > 0x7f for c in rst): # Trac detects utf-8 using BOM rst = '%s.. charset=utf-8\n\n%s' % (codecs.BOM_UTF8, rst) return rst + '\n' def main(): names = sorted(name for name in resource_listdir('trac.wiki', 'default-pages') if not name.startswith('.')) env = EnvironmentStub() load_components(env) with env.db_transaction: for name in names: wiki = WikiPage(env, name) wiki.text = resource_string('trac.wiki', 'default-pages/' + name).decode('utf-8') if wiki.text: wiki.save('trac', '') else: printout('%s: Skipped empty page' % name) req = Mock(href=Href('/'), abs_href=Href('http://trac.edgewall.org/'), perm=MockPerm(), chrome={}) for name in sys.argv[1:]: name = os.path.basename(name) wiki = WikiPage(env, name) if not wiki.exists: continue context = web_context(req, wiki.resource, absurls=True) rst = wiki2rest(env, context, wiki) sys.stdout.write(rst) if __name__ == '__main__': main() trac-1.0.9+dfsg/contrib/l10n_revert_lineno_conflicts.py0000644000175000017500000000417712574541640021331 0ustar wmbwmb# -*- coding: utf-8 -*- # # Copyright (C) 2013 Edgewall Software # Copyright (C) 2013 Christian Boos # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://trac.edgewall.org/. """ L10N tool which takes a list of .po in conflicted state and revert ignorable changes. It resolve the conflicts for which "theirs" changes consist solely of line number changes, by reverting to the working copy content. This makes it easier to merge translation .po files across branches. """ import re ignore_lineno_re = re.compile(r''' <<<< .* \n ( (?: [^=] .* \n )+ ) # \1 == "working copy" ==== .* \n ( (?: \# .* \n )+ ) # \2 == comment only for "theirs" >>>> .* \n ''', re.MULTILINE | re.VERBOSE) HEADERS = ''' Project-Id-Version Report-Msgid-Bugs-To POT-Creation-Date PO-Revision-Date Last-Translator Language-Team Plural-Forms MIME-Version Content-Type Content-Transfer-Encoding Generated-By '''.split() po_headers_re = re.compile(r''' <<<< .* \n ( (?: "(?:%(header)s): \s [^"]+" \n )+ ) # \1 == "working copy" ==== .* \n ( (?: "(?:%(header)s): \s [^"]+" \n )+ ) # \2 == another date for "theirs" >>>> .* \n ''' % dict(header='|'.join(HEADERS)), re. MULTILINE | re.VERBOSE) def sanitize_file(path): with open(path, 'r+') as f: sanitized, nsub = ignore_lineno_re.subn(r'\1', f.read()) sanitized, nsub2 = po_headers_re.subn(r'\1', sanitized) nsub += nsub2 if nsub: print("reverted %d ignorable changes in %s" % (nsub, path)) f.seek(0) f.write(sanitized) f.truncate() else: print("no ignorable changes in %s" % (path,)) if __name__ == '__main__': import sys for path in sys.argv[1:]: sanitize_file(path) trac-1.0.9+dfsg/contrib/checkwiki.py0000755000175000017500000001461012574541640015515 0ustar wmbwmb#!/usr/bin/env python # -*- coding: utf-8 -*- # # Copyright (C) 2015 Edgewall Software # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://trac.edgewall.org/. from __future__ import with_statement import os import sys from pkg_resources import resource_listdir, resource_string from trac.loader import load_components from trac.test import EnvironmentStub, Mock, MockPerm from trac.util.text import printout from trac.web.chrome import web_context from trac.web.href import Href from trac.wiki.formatter import Formatter from trac.wiki.model import WikiPage TURN_ON = '\033[30m\033[41m' TURN_OFF = '\033[m' class DefaultWikiChecker(Formatter): def __init__(self, env, context, name): Formatter.__init__(self, env, context) self.__name = name self.__marks = [] self.__super = super(DefaultWikiChecker, self) def handle_match(self, fullmatch): rv = self.__super.handle_match(fullmatch) if rv: if not isinstance(rv, basestring): text = unicode(rv) else: text = rv if text.startswith('') and \ 'class="missing ' in text: self.__marks.append((fullmatch.start(0), fullmatch.end(0))) return rv def handle_code_block(self, line, startmatch=None): prev_processor = getattr(self, 'code_processor', None) try: return self.__super.handle_code_block(line, startmatch) finally: processor = self.code_processor if startmatch and processor and processor != prev_processor and \ processor.error: self.__marks.append((startmatch.start(0), startmatch.end(0))) def format(self, text, out=None): return self.__super.format(SourceWrapper(self, text), out) def next_callback(self, line, idx): marks = self.__marks if marks: buf = [] prev = 0 for start, end in self.__marks: buf.append(line[prev:start]) buf.append(TURN_ON) buf.append(line[start:end]) buf.append(TURN_OFF) prev = end buf.append(line[prev:]) printout('%s:%d:%s' % (self.__name, idx + 1, ''.join(buf))) self.__marks[:] = () class SourceWrapper(object): def __init__(self, formatter, text): self.formatter = formatter self.text = text def __iter__(self): return LinesIterator(self.formatter, self.text.splitlines()) class LinesIterator(object): def __init__(self, formatter, lines): self.formatter = formatter self.lines = lines self.idx = 0 self.current = None def next(self): idx = self.idx if self.current is not None: self.formatter.next_callback(self.current, idx) if idx >= len(self.lines): self.current = None raise StopIteration self.idx = idx + 1 self.current = self.lines[idx] return self.current class DummyIO(object): def write(self, data): pass def parse_args(): from optparse import OptionParser parser = OptionParser(usage='Usage: %prog [options] [PAGES...]') parser.add_option('-d', '--download', dest='download', default=False, action='store_true', help='Download default pages from trac.edgewall.org ' 'before checking') parser.add_option('-p', '--prefix', dest='prefix', default='', help='Prepend "prefix/" to the page when downloading') return parser.parse_args() def download_default_pages(names, prefix): from httplib import HTTPConnection host = 'trac.edgewall.org' if prefix and not prefix.endswith('/'): prefix += '/' conn = HTTPConnection(host) for name in names: if name in ('WikiStart', 'SandBox'): continue sys.stdout.write('Downloading %s%s' % (prefix, name)) conn.request('GET', '/wiki/%s%s?format=txt' % (prefix, name)) response = conn.getresponse() content = response.read() if prefix and (response.status != 200 or not content): sys.stdout.write(' %s' % name) conn.request('GET', '/wiki/%s?format=txt' % name) response = conn.getresponse() content = response.read() if response.status == 200 and content: with open('trac/wiki/default-pages/' + name, 'w') as f: lines = content.replace('\r\n', '\n').splitlines(True) f.write(''.join(line for line in lines if line.strip() != '[[TranslatedPages]]')) sys.stdout.write('\tdone.\n') else: sys.stdout.write('\tmissing or empty.\n') conn.close() def main(): options, args = parse_args() names = sorted(name for name in resource_listdir('trac.wiki', 'default-pages') if not name.startswith('.')) if args: args = sorted(set(names) & set(map(os.path.basename, args))) else: args = names if options.download: download_default_pages(args, options.prefix) env = EnvironmentStub(disable=['trac.mimeview.pygments.*']) load_components(env) with env.db_transaction: for name in names: wiki = WikiPage(env, name) wiki.text = resource_string('trac.wiki', 'default-pages/' + name).decode('utf-8') if wiki.text: wiki.save('trac', '') else: printout('%s: Skipped empty page' % name) req = Mock(href=Href('/'), abs_href=Href('http://localhost/'), perm=MockPerm()) for name in args: wiki = WikiPage(env, name) if not wiki.exists: continue context = web_context(req, wiki.resource) out = DummyIO() DefaultWikiChecker(env, context, name).format(wiki.text, out) if __name__ == '__main__': main() trac-1.0.9+dfsg/contrib/cgi-bin/0000755000175000017500000000000012574541731014506 5ustar wmbwmbtrac-1.0.9+dfsg/contrib/cgi-bin/trac.cgi0000755000175000017500000000205712574541640016131 0ustar wmbwmb#!/usr/bin/python # -*- coding: utf-8 -*- # # Copyright (C) 2003-2013 Edgewall Software # Copyright (C) 2003-2004 Jonas Borgström # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.org/wiki/TracLicense. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://trac.edgewall.org/log/. # # Author: Jonas Borgström try: from trac.web import cgi_frontend cgi_frontend.run() except SystemExit: raise except Exception, e: import sys import traceback print>>sys.stderr, e traceback.print_exc(file=sys.stderr) print 'Status: 500 Internal Server Error' print 'Content-Type: text/plain' print print 'Oops...' print print 'Trac detected an internal error:', e print traceback.print_exc(file=sys.stdout) trac-1.0.9+dfsg/contrib/cgi-bin/trac.fcgi0000755000175000017500000000200312574541640016266 0ustar wmbwmb#!/usr/bin/python # -*- coding: utf-8 -*- # # Copyright (C) 2003-2013 Edgewall Software # Copyright (C) 2003-2004 Jonas Borgström # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.org/wiki/TracLicense. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://trac.edgewall.org/log/. # # Author: Jonas Borgström try: from trac.web import fcgi_frontend fcgi_frontend.run() except SystemExit: raise except Exception, e: print 'Content-Type: text/plain\r\n\r\n', print 'Oops...' print print 'Trac detected an internal error:' print print e print import traceback import StringIO tb = StringIO.StringIO() traceback.print_exc(file=tb) print tb.getvalue() trac-1.0.9+dfsg/contrib/migrateticketmodel.py0000644000175000017500000000341712574541640017431 0ustar wmbwmb#!/usr/bin/env python # -*- coding: utf-8 -*- # # Copyright (C) 2005-2013 Edgewall Software # Copyright (C) 2005 Christopher Lenz # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://trac.edgewall.org/. # This script completely migrates a <= 0.8.x Trac environment to use the new # default ticket model introduced in Trac 0.9. # # In particular, this means that the severity field is removed (or rather # disabled by removing all possible values), and the priority values are # changed to the more meaningful new defaults. # # Make sure to make a backup of the Trac environment before running this! from __future__ import with_statement import os import sys from trac.env import open_environment from trac.ticket.model import Priority, Severity priority_mapping = { 'highest': 'blocker', 'high': 'critical', 'normal': 'major', 'low': 'minor', 'lowest': 'trivial' } def main(): if len(sys.argv) < 2: print >> sys.stderr, 'usage: %s /path/to/projenv' \ % os.path.basename(sys.argv[0]) sys.exit(2) env = open_environment(sys.argv[1]) with env.db_transaction: for oldprio, newprio in priority_mapping.items(): priority = Priority(env, oldprio) priority.name = newprio priority.update() for severity in list(Severity.select(env)): severity.delete() if __name__ == '__main__': main() trac-1.0.9+dfsg/contrib/htpasswd.py0000755000175000017500000001102612574541640015407 0ustar wmbwmb#!/usr/bin/env python # -*- coding: utf-8 -*- # # Copyright (C) 2008-2013 Edgewall Software # Copyright (C) 2008 Eli Carter # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://trac.edgewall.org/. """Replacement for htpasswd""" import os import sys import random from optparse import OptionParser from trac.util.compat import wait_for_file_mtime_change # We need a crypt module, but Windows doesn't have one by default. Try to find # one, and tell the user if we can't. try: import crypt except ImportError: try: import fcrypt as crypt except ImportError: sys.stderr.write("Cannot find a crypt module. " "Possibly http://carey.geek.nz/code/python-fcrypt/\n") sys.exit(1) def salt(): """Returns a string of 2 randome letters""" letters = 'abcdefghijklmnopqrstuvwxyz' \ 'ABCDEFGHIJKLMNOPQRSTUVWXYZ' \ '0123456789/.' return random.choice(letters) + random.choice(letters) class HtpasswdFile: """A class for manipulating htpasswd files.""" def __init__(self, filename, create=False): self.entries = [] self.filename = filename if not create: if os.path.exists(self.filename): self.load() else: raise Exception("%s does not exist" % self.filename) def load(self): """Read the htpasswd file into memory.""" lines = open(self.filename, 'r').readlines() self.entries = [] for line in lines: username, pwhash = line.split(':') entry = [username, pwhash.rstrip()] self.entries.append(entry) def save(self): """Write the htpasswd file to disk""" wait_for_file_mtime_change(self.filename) open(self.filename, 'w').writelines(["%s:%s\n" % (entry[0], entry[1]) for entry in self.entries]) def update(self, username, password): """Replace the entry for the given user, or add it if new.""" pwhash = crypt.crypt(password, salt()) matching_entries = [entry for entry in self.entries if entry[0] == username] if matching_entries: matching_entries[0][1] = pwhash else: self.entries.append([username, pwhash]) def delete(self, username): """Remove the entry for the given user.""" self.entries = [entry for entry in self.entries if entry[0] != username] def main(): """ %prog -b[c] filename username password %prog -D filename username""" # For now, we only care about the use cases that affect tests/functional.py parser = OptionParser(usage=main.__doc__) parser.add_option('-b', action='store_true', dest='batch', default=False, help='Batch mode; password is passed on the command line IN THE CLEAR.' ) parser.add_option('-c', action='store_true', dest='create', default=False, help='Create a new htpasswd file, overwriting any existing file.') parser.add_option('-D', action='store_true', dest='delete_user', default=False, help='Remove the given user from the password file.') options, args = parser.parse_args() def syntax_error(msg): """Utility function for displaying fatal error messages with usage help. """ sys.stderr.write("Syntax error: " + msg) sys.stderr.write(parser.get_usage()) sys.exit(1) if not (options.batch or options.delete_user): syntax_error("Only batch and delete modes are supported\n") # Non-option arguments if len(args) < 2: syntax_error("Insufficient number of arguments.\n") filename, username = args[:2] if options.delete_user: if len(args) != 2: syntax_error("Incorrect number of arguments.\n") password = None else: if len(args) != 3: syntax_error("Incorrect number of arguments.\n") password = args[2] passwdfile = HtpasswdFile(filename, create=options.create) if options.delete_user: passwdfile.delete(username) else: passwdfile.update(username, password) passwdfile.save() if __name__ == '__main__': main() trac-1.0.9+dfsg/contrib/trac-pre-commit-hook0000644000175000017500000000450612574541640017066 0ustar wmbwmb#!/usr/bin/env python # -*- coding: utf-8 -*- # # Copyright (C) 2004-2013 Edgewall Software # Copyright (C) 2004 Jonas Borgström # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://trac.edgewall.org/. # # This script will enforce the following policy: # # "A checkin must reference an open ticket." # # This script should be invoked from the subversion pre-commit hook like this: # # REPOS="$1" # TXN="$2" # TRAC_ENV="/somewhere/trac/project/" # LOG=`/usr/bin/svnlook log -t "$TXN" "$REPOS"` # /usr/bin/python /some/path/trac-pre-commit-hook "$TRAC_ENV" "$LOG" || exit 1 # import os import re import sys if not 'PYTHON_EGG_CACHE' in os.environ: os.environ['PYTHON_EGG_CACHE'] = os.path.join(sys.argv[1], '.egg-cache') from trac.env import open_environment def main(): if len(sys.argv) != 3: print >> sys.stderr, 'Usage: %s ' % sys.argv[0] sys.exit(1) env_path = sys.argv[1] log = sys.argv[2] tickets = [] for tmp in re.findall('(?:close|closed|closes|fix|fixed|fixes|addresses|references|refs|re|see)' '.?(#[0-9]+(?:(?:[, &]+| *and *)#[0-9]+)*)', log.lower()): tickets += re.findall('#([0-9]+)', tmp) # At least one ticket has to be mentioned in the log message if tickets == []: print >> sys.stderr, 'At least one open ticket must be mentioned ' \ 'in the log message.' sys.exit(1) env = open_environment(env_path) db = env.get_db_cnx() cursor = db.cursor() cursor.execute("SELECT COUNT(id) FROM ticket WHERE " "status <> 'closed' AND id IN (%s)" % ','.join(tickets)) row = cursor.fetchone() # At least one of the tickets mentioned in the log messages has to # be open if not row or row[0] < 1: print >> sys.stderr, 'At least one open ticket must be mentioned ' \ 'in the log message.' sys.exit(1) else: sys.exit(0) if __name__ == '__main__': main() trac-1.0.9+dfsg/contrib/bugzilla2trac.py0000644000175000017500000010763312574541640016326 0ustar wmbwmb#!/usr/bin/env python # -*- coding: utf-8 -*- # # Copyright (C) 2004-2013 Edgewall Software # Copyright (C) 2004 Dmitry Yusupov # Copyright (C) 2004 Mark Rowe # Copyright (C) 2005 Bill Soudan # Copyright (C) 2005 Florent Guillaume # Copyright (C) 2005 Jeroen Ruigrok van der Werven # Copyright (C) 2010 Jeff Moreland # # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://trac.edgewall.org/. """ Import a Bugzilla items into a Trac database. Requires: Trac 0.9b1 from http://trac.edgewall.org/ Python 2.3 from http://www.python.org/ MySQL >= 3.23 from http://www.mysql.org/ or PostGreSQL 8.4 from http://www.postgresql.org/ or SQLite 3 from http://www.sqlite.org/ $Id: bugzilla2trac.py 13166 2014-10-26 04:24:20Z rjollos $ """ from __future__ import with_statement import re ### ### Conversion Settings -- edit these before running if desired ### # Bugzilla version. You can find this in Bugzilla's globals.pl file. # # Currently, the following bugzilla versions are known to work: # 2.11 (2110), 2.16.5 (2165), 2.16.7 (2167), 2.18.3 (2183), 2.19.1 (2191), # 2.23.3 (2233), 3.04.4 (3044) # # If you run this script on a version not listed here and it is successful, # please file a ticket at http://trac.edgewall.org # BZ_VERSION = 3044 # MySQL connection parameters for the Bugzilla database. These can also # be specified on the command line. BZ_DB = "" BZ_HOST = "" BZ_USER = "" BZ_PASSWORD = "" # Path to the Trac environment. TRAC_ENV = "/usr/local/trac" # If true, all existing Trac tickets and attachments will be removed # prior to import. TRAC_CLEAN = True # Enclose imported ticket description and comments in a {{{ }}} # preformat block? This formats the text in a fixed-point font. PREFORMAT_COMMENTS = False # Replace bug numbers in comments with #xyz REPLACE_BUG_NO = False # Severities SEVERITIES = [ ("blocker", "1"), ("critical", "2"), ("major", "3"), ("normal", "4"), ("minor", "5"), ("trivial", "6") ] # Priorities # If using the default Bugzilla priorities of P1 - P5, do not change anything # here. # If you have other priorities defined please change the P1 - P5 mapping to # the order you want. You can also collapse multiple priorities on bugzilla's # side into the same priority on Trac's side, simply adjust PRIORITIES_MAP. PRIORITIES = [ ("highest", "1"), ("high", "2"), ("normal", "3"), ("low", "4"), ("lowest", "5") ] # Bugzilla: Trac # NOTE: Use lowercase. PRIORITIES_MAP = { "p1": "highest", "p2": "high", "p3": "normal", "p4": "low", "p5": "lowest" } # By default, all bugs are imported from Bugzilla. If you add a list # of products here, only bugs from those products will be imported. PRODUCTS = [] # These Bugzilla products will be ignored during import. IGNORE_PRODUCTS = [] # These milestones are ignored IGNORE_MILESTONES = ["---"] # Don't import user names and passwords into htpassword if # user is disabled in bugzilla? (i.e. profiles.DisabledText<>'') IGNORE_DISABLED_USERS = True # These logins are converted to these user ids LOGIN_MAP = { #'some.user@example.com': 'someuser', } # These emails are removed from CC list IGNORE_CC = [ #'loser@example.com', ] # The 'component' field in Trac can come either from the Product or # or from the Component field of Bugzilla. COMPONENTS_FROM_PRODUCTS # switches the behavior. # If COMPONENTS_FROM_PRODUCTS is True: # - Bugzilla Product -> Trac Component # - Bugzilla Component -> Trac Keyword # IF COMPONENTS_FROM_PRODUCTS is False: # - Bugzilla Product -> Trac Keyword # - Bugzilla Component -> Trac Component COMPONENTS_FROM_PRODUCTS = False # If COMPONENTS_FROM_PRODUCTS is True, the default owner for each # Trac component is inferred from a default Bugzilla component. DEFAULT_COMPONENTS = ["default", "misc", "main"] # This mapping can assign keywords in the ticket entry to represent # products or components (depending on COMPONENTS_FROM_PRODUCTS). # The keyword will be ignored if empty. KEYWORDS_MAPPING = { #'Bugzilla_product_or_component': 'Keyword', "default": "", "misc": "", } # If this is True, products or components are all set as keywords # even if not mentionned in KEYWORDS_MAPPING. MAP_ALL_KEYWORDS = True # Custom field mappings CUSTOMFIELD_MAP = { #'Bugzilla_field_name': 'Trac_customfield_name', #'op_sys': 'os', #'cf_featurewantedby': 'wanted_by', #'product': 'product' } # Bug comments that should not be imported. Each entry in list should # be a regular expression. IGNORE_COMMENTS = [ "^Created an attachment \(id=" ] ########################################################################### ### You probably don't need to change any configuration past this line. ### ########################################################################### # Bugzilla status to Trac status translation map. # # NOTE: bug activity is translated as well, which may cause bug # activity to be deleted (e.g. resolved -> closed in Bugzilla # would translate into closed -> closed in Trac, so we just ignore the # change). # # There is some special magic for open in the code: if there is no # Bugzilla owner, open is mapped to 'new' instead. STATUS_TRANSLATE = { "unconfirmed": "new", "open": "assigned", "resolved": "closed", "verified": "closed", "released": "closed" } # Translate Bugzilla statuses into Trac keywords. This provides a way # to retain the Bugzilla statuses in Trac. e.g. when a bug is marked # 'verified' in Bugzilla it will be assigned a VERIFIED keyword. STATUS_KEYWORDS = { "verified": "VERIFIED", "released": "RELEASED" } # Some fields in Bugzilla do not have equivalents in Trac. Changes in # fields listed here will not be imported into the ticket change history, # otherwise you'd see changes for fields that don't exist in Trac. IGNORED_ACTIVITY_FIELDS = ["everconfirmed"] # Regular expression and its replacement # this expression will update references to bugs 1 - 99999 that # have the form "bug 1" or "bug #1" BUG_NO_RE = re.compile(r"\b(bug #?)([0-9]{1,5})\b", re.I) BUG_NO_REPL = r"#\2" ### ### Script begins here ### import os import sys import string import StringIO import MySQLdb import MySQLdb.cursors from trac.attachment import Attachment from trac.env import Environment if not hasattr(sys, 'setdefaultencoding'): reload(sys) sys.setdefaultencoding('latin1') # simulated Attachment class for trac.add #class Attachment: # def __init__(self, name, data): # self.filename = name # self.file = StringIO.StringIO(data.tostring()) # simple field translation mapping. if string not in # mapping, just return string, otherwise return value class FieldTranslator(dict): def __getitem__(self, item): if item not in self: return item return dict.__getitem__(self, item) statusXlator = FieldTranslator(STATUS_TRANSLATE) class TracDatabase(object): def __init__(self, path): self.env = Environment(path) self.loginNameCache = {} self.fieldNameCache = {} from trac.db.api import DatabaseManager self.using_postgres = \ DatabaseManager(self.env).connection_uri.startswith("postgres:") def hasTickets(self): return int(self.env.db_query("SELECT count(*) FROM ticket")[0][0] > 0) def assertNoTickets(self): if self.hasTickets(): raise Exception("Will not modify database with existing tickets!") def setSeverityList(self, s): """Remove all severities, set them to `s`""" self.assertNoTickets() with self.env.db_transaction as db: db("DELETE FROM enum WHERE type='severity'") for value, i in s: print " inserting severity '%s' - '%s'" % (value, i) db("""INSERT INTO enum (type, name, value) VALUES (%s, %s, %s)""", ("severity", value, i)) def setPriorityList(self, s): """Remove all priorities, set them to `s`""" self.assertNoTickets() with self.env.db_transaction as db: db("DELETE FROM enum WHERE type='priority'") for value, i in s: print " inserting priority '%s' - '%s'" % (value, i) db("INSERT INTO enum (type, name, value) VALUES (%s, %s, %s)", ("priority", value, i)) def setComponentList(self, l, key): """Remove all components, set them to `l`""" self.assertNoTickets() with self.env.db_transaction as db: db("DELETE FROM component") for comp in l: print " inserting component '%s', owner '%s'" % \ (comp[key], comp['owner']) db("INSERT INTO component (name, owner) VALUES (%s, %s)", (comp[key], comp['owner'])) def setVersionList(self, v, key): """Remove all versions, set them to `v`""" self.assertNoTickets() with self.env.db_transaction as db: db("DELETE FROM version") for vers in v: print " inserting version '%s'" % (vers[key]) db("INSERT INTO version (name) VALUES (%s)", (vers[key],)) def setMilestoneList(self, m, key): """Remove all milestones, set them to `m`""" self.assertNoTickets() with self.env.db_transaction as db: db("DELETE FROM milestone") for ms in m: milestone = ms[key] print " inserting milestone '%s'" % (milestone) db("INSERT INTO milestone (name) VALUES (%s)", (milestone,)) def addTicket(self, id, time, changetime, component, severity, priority, owner, reporter, cc, version, milestone, status, resolution, summary, description, keywords, customfields): desc = description type = "defect" if SEVERITIES: if severity.lower() == "enhancement": severity = "minor" type = "enhancement" else: if priority.lower() == "enhancement": priority = "minor" type = "enhancement" if PREFORMAT_COMMENTS: desc = '{{{\n%s\n}}}' % desc if REPLACE_BUG_NO: if BUG_NO_RE.search(desc): desc = re.sub(BUG_NO_RE, BUG_NO_REPL, desc) if priority in PRIORITIES_MAP: priority = PRIORITIES_MAP[priority] print " inserting ticket %s -- %s" % (id, summary) with self.env.db_transaction as db: db("""INSERT INTO ticket (id, type, time, changetime, component, severity, priority, owner, reporter, cc, version, milestone, status, resolution, summary, description, keywords) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s) """, (id, type, datetime2epoch(time), datetime2epoch(changetime), component, severity, priority, owner, reporter, cc, version, milestone, status.lower(), resolution, summary, desc, keywords)) if self.using_postgres: with self.env.db_transaction as db: c = db.cursor() c.execute(""" SELECT SETVAL('ticket_id_seq', MAX(id)) FROM ticket; SELECT SETVAL('report_id_seq', MAX(id)) FROM report""") ticket_id = db.get_last_id(c, 'ticket') # add all custom fields to ticket for name, value in customfields.iteritems(): self.addTicketCustomField(ticket_id, name, value) return ticket_id def addTicketCustomField(self, ticket_id, field_name, field_value): if field_value == None: return self.env.db_transaction(""" INSERT INTO ticket_custom (ticket, name, value) VALUES (%s, %s, %s) """, (ticket_id, field_name, field_value)) def addTicketComment(self, ticket, time, author, value): comment = value if PREFORMAT_COMMENTS: comment = '{{{\n%s\n}}}' % comment if REPLACE_BUG_NO: if BUG_NO_RE.search(comment): comment = re.sub(BUG_NO_RE, BUG_NO_REPL, comment) with self.env.db_transaction as db: db("""INSERT INTO ticket_change (ticket, time, author, field, oldvalue, newvalue) VALUES (%s, %s, %s, %s, %s, %s) """, (ticket, datetime2epoch(time), author, 'comment', '', comment)) def addTicketChange(self, ticket, time, author, field, oldvalue, newvalue): if field == "owner": if oldvalue in LOGIN_MAP: oldvalue = LOGIN_MAP[oldvalue] if newvalue in LOGIN_MAP: newvalue = LOGIN_MAP[newvalue] if field == "priority": if oldvalue.lower() in PRIORITIES_MAP: oldvalue = PRIORITIES_MAP[oldvalue.lower()] if newvalue.lower() in PRIORITIES_MAP: newvalue = PRIORITIES_MAP[newvalue.lower()] # Doesn't make sense if we go from highest -> highest, for example. if oldvalue == newvalue: return with self.env.db_transaction as db: db("""INSERT INTO ticket_change (ticket, time, author, field, oldvalue, newvalue) VALUES (%s, %s, %s, %s, %s, %s) """, (ticket, datetime2epoch(time), author, field, oldvalue, newvalue)) def addAttachment(self, author, a): if a['filename'] != '': description = a['description'] id = a['bug_id'] filename = a['filename'] filedata = StringIO.StringIO(a['thedata']) filesize = len(filedata.getvalue()) time = a['creation_ts'] print " ->inserting attachment '%s' for ticket %s -- %s" % \ (filename, id, description) attachment = Attachment(self.env, 'ticket', id) attachment.author = author attachment.description = description attachment.insert(filename, filedata, filesize, datetime2epoch(time)) del attachment def getLoginName(self, cursor, userid): if userid not in self.loginNameCache: cursor.execute("SELECT * FROM profiles WHERE userid = %s", (userid)) loginName = cursor.fetchall() if loginName: loginName = loginName[0]['login_name'] else: print """WARNING: unknown bugzilla userid %d, recording as anonymous""" % (userid) loginName = "anonymous" loginName = LOGIN_MAP.get(loginName, loginName) self.loginNameCache[userid] = loginName return self.loginNameCache[userid] def getFieldName(self, cursor, fieldid): if fieldid not in self.fieldNameCache: # fielddefs.fieldid got changed to fielddefs.id in Bugzilla # 2.23.3. if BZ_VERSION >= 2233: cursor.execute("SELECT * FROM fielddefs WHERE id = %s", (fieldid)) else: cursor.execute("SELECT * FROM fielddefs WHERE fieldid = %s", (fieldid)) fieldName = cursor.fetchall() if fieldName: fieldName = fieldName[0]['name'].lower() else: print "WARNING: unknown bugzilla fieldid %d, \ recording as unknown" % (userid) fieldName = "unknown" self.fieldNameCache[fieldid] = fieldName return self.fieldNameCache[fieldid] def makeWhereClause(fieldName, values, negative=False): if not values: return '' if negative: connector, op = ' AND ', '!=' else: connector, op = ' OR ', '=' clause = connector.join(["%s %s '%s'" % (fieldName, op, value) for value in values]) return ' (' + clause + ')' def convert(_db, _host, _user, _password, _env, _force): activityFields = FieldTranslator() # account for older versions of bugzilla print "Using Bugzilla v%s schema." % BZ_VERSION if BZ_VERSION == 2110: activityFields['removed'] = "oldvalue" activityFields['added'] = "newvalue" # init Bugzilla environment print "Bugzilla MySQL('%s':'%s':'%s':'%s'): connecting..." % \ (_db, _host, _user, ("*" * len(_password))) mysql_con = MySQLdb.connect(host=_host, user=_user, passwd=_password, db=_db, compress=1, cursorclass=MySQLdb.cursors.DictCursor, charset='utf8') mysql_cur = mysql_con.cursor() # init Trac environment print "Trac SQLite('%s'): connecting..." % (_env) trac = TracDatabase(_env) # force mode... if _force == 1: print "\nCleaning all tickets..." with trac.env.db_transaction as db: db("DELETE FROM ticket_change") db("DELETE FROM ticket") db("DELETE FROM ticket_custom") db("DELETE FROM attachment") attachments_dir = os.path.join(os.path.normpath(trac.env.path), "attachments") # Straight from the Python documentation. for root, dirs, files in os.walk(attachments_dir, topdown=False): for name in files: os.remove(os.path.join(root, name)) for name in dirs: os.rmdir(os.path.join(root, name)) if not os.stat(attachments_dir): os.mkdir(attachments_dir) print "All tickets cleaned..." print "\n0. Filtering products..." if BZ_VERSION >= 2180: mysql_cur.execute("SELECT name FROM products") else: mysql_cur.execute("SELECT product AS name FROM products") products = [] for line in mysql_cur.fetchall(): product = line['name'] if PRODUCTS and product not in PRODUCTS: continue if product in IGNORE_PRODUCTS: continue products.append(product) PRODUCTS[:] = products print " Using products", " ".join(PRODUCTS) print "\n1. Import severities..." trac.setSeverityList(SEVERITIES) print "\n2. Import components..." if not COMPONENTS_FROM_PRODUCTS: if BZ_VERSION >= 2180: sql = """SELECT DISTINCT c.name AS name, c.initialowner AS owner FROM components AS c, products AS p WHERE c.product_id = p.id AND""" sql += makeWhereClause('p.name', PRODUCTS) else: sql = "SELECT value AS name, initialowner AS owner FROM components" sql += " WHERE" + makeWhereClause('program', PRODUCTS) mysql_cur.execute(sql) components = mysql_cur.fetchall() for component in components: component['owner'] = trac.getLoginName(mysql_cur, component['owner']) trac.setComponentList(components, 'name') else: if BZ_VERSION >= 2180: sql = ("SELECT p.name AS product, c.name AS comp, " " c.initialowner AS owner " "FROM components c, products p " "WHERE c.product_id = p.id AND" + makeWhereClause('p.name', PRODUCTS)) else: sql = ("SELECT program AS product, value AS comp, " " initialowner AS owner " "FROM components WHERE" + makeWhereClause('program', PRODUCTS)) mysql_cur.execute(sql) lines = mysql_cur.fetchall() all_components = {} # product -> components all_owners = {} # product, component -> owner for line in lines: product = line['product'] comp = line['comp'] owner = line['owner'] all_components.setdefault(product, []).append(comp) all_owners[(product, comp)] = owner component_list = [] for product, components in all_components.items(): # find best default owner default = None for comp in DEFAULT_COMPONENTS: if comp in components: default = comp break if default is None: default = components[0] owner = all_owners[(product, default)] owner_name = trac.getLoginName(mysql_cur, owner) component_list.append({'product': product, 'owner': owner_name}) trac.setComponentList(component_list, 'product') print "\n3. Import priorities..." trac.setPriorityList(PRIORITIES) print "\n4. Import versions..." if BZ_VERSION >= 2180: sql = """SELECT DISTINCTROW v.value AS value FROM products p, versions v""" sql += " WHERE v.product_id = p.id AND" sql += makeWhereClause('p.name', PRODUCTS) else: sql = "SELECT DISTINCTROW value FROM versions" sql += " WHERE" + makeWhereClause('program', PRODUCTS) mysql_cur.execute(sql) versions = mysql_cur.fetchall() trac.setVersionList(versions, 'value') print "\n5. Import milestones..." sql = "SELECT DISTINCT value FROM milestones" sql += " WHERE" + makeWhereClause('value', IGNORE_MILESTONES, negative=True) mysql_cur.execute(sql) milestones = mysql_cur.fetchall() trac.setMilestoneList(milestones, 'value') print "\n6. Retrieving bugs..." if BZ_VERSION >= 2180: sql = """SELECT DISTINCT b.*, c.name AS component, p.name AS product FROM bugs AS b, components AS c, products AS p """ sql += " WHERE" + makeWhereClause('p.name', PRODUCTS) sql += " AND b.product_id = p.id" sql += " AND b.component_id = c.id" sql += " ORDER BY b.bug_id" else: sql = """SELECT DISTINCT b.*, c.value AS component, p.product AS product FROM bugs AS b, components AS c, products AS p """ sql += " WHERE" + makeWhereClause('p.product', PRODUCTS) sql += " AND b.product = p.product" sql += " AND b.component = c.value" sql += " ORDER BY b.bug_id" mysql_cur.execute(sql) bugs = mysql_cur.fetchall() print "\n7. Import bugs and bug activity..." for bug in bugs: bugid = bug['bug_id'] ticket = {} keywords = [] ticket['id'] = bugid ticket['time'] = bug['creation_ts'] ticket['changetime'] = bug['delta_ts'] if COMPONENTS_FROM_PRODUCTS: ticket['component'] = bug['product'] else: ticket['component'] = bug['component'] if SEVERITIES: ticket['severity'] = bug['bug_severity'] ticket['priority'] = bug['priority'].lower() else: # use bugzilla severities as trac priorities, and ignore bugzilla # priorities ticket['severity'] = '' ticket['priority'] = bug['bug_severity'] ticket['owner'] = trac.getLoginName(mysql_cur, bug['assigned_to']) ticket['reporter'] = trac.getLoginName(mysql_cur, bug['reporter']) # pack bugzilla fields into dictionary of trac custom field # names and values customfields = {} for bugfield, customfield in CUSTOMFIELD_MAP.iteritems(): customfields[customfield] = bug[bugfield] ticket['customfields'] = customfields mysql_cur.execute("SELECT * FROM cc WHERE bug_id = %s", bugid) cc_records = mysql_cur.fetchall() cc_list = [] for cc in cc_records: cc_list.append(trac.getLoginName(mysql_cur, cc['who'])) cc_list = [cc for cc in cc_list if cc not in IGNORE_CC] ticket['cc'] = string.join(cc_list, ', ') ticket['version'] = bug['version'] target_milestone = bug['target_milestone'] if target_milestone in IGNORE_MILESTONES: target_milestone = '' ticket['milestone'] = target_milestone bug_status = bug['bug_status'].lower() ticket['status'] = statusXlator[bug_status] ticket['resolution'] = bug['resolution'].lower() # a bit of extra work to do open tickets if bug_status == 'open': if owner != '': ticket['status'] = 'assigned' else: ticket['status'] = 'new' ticket['summary'] = bug['short_desc'] mysql_cur.execute("SELECT * FROM longdescs WHERE bug_id = %s" % bugid) longdescs = list(mysql_cur.fetchall()) # check for empty 'longdescs[0]' field... if len(longdescs) == 0: ticket['description'] = '' else: ticket['description'] = longdescs[0]['thetext'] del longdescs[0] for desc in longdescs: ignore = False for comment in IGNORE_COMMENTS: if re.match(comment, desc['thetext']): ignore = True if ignore: continue trac.addTicketComment(ticket=bugid, time = desc['bug_when'], author=trac.getLoginName(mysql_cur, desc['who']), value = desc['thetext']) mysql_cur.execute("""SELECT * FROM bugs_activity WHERE bug_id = %s ORDER BY bug_when""" % bugid) bugs_activity = mysql_cur.fetchall() resolution = '' ticketChanges = [] keywords = [] for activity in bugs_activity: field_name = trac.getFieldName(mysql_cur, activity['fieldid']).lower() removed = activity[activityFields['removed']] added = activity[activityFields['added']] # statuses and resolutions are in lowercase in trac if field_name == "resolution" or field_name == "bug_status": removed = removed.lower() added = added.lower() # remember most recent resolution, we need this later if field_name == "resolution": resolution = added.lower() add_keywords = [] remove_keywords = [] # convert bugzilla field names... if field_name == "bug_severity": if SEVERITIES: field_name = "severity" else: field_name = "priority" elif field_name == "assigned_to": field_name = "owner" elif field_name == "bug_status": field_name = "status" if removed in STATUS_KEYWORDS: remove_keywords.append(STATUS_KEYWORDS[removed]) if added in STATUS_KEYWORDS: add_keywords.append(STATUS_KEYWORDS[added]) added = statusXlator[added] removed = statusXlator[removed] elif field_name == "short_desc": field_name = "summary" elif field_name == "product" and COMPONENTS_FROM_PRODUCTS: field_name = "component" elif ((field_name == "product" and not COMPONENTS_FROM_PRODUCTS) or (field_name == "component" and COMPONENTS_FROM_PRODUCTS)): if MAP_ALL_KEYWORDS or removed in KEYWORDS_MAPPING: kw = KEYWORDS_MAPPING.get(removed, removed) if kw: remove_keywords.append(kw) if MAP_ALL_KEYWORDS or added in KEYWORDS_MAPPING: kw = KEYWORDS_MAPPING.get(added, added) if kw: add_keywords.append(kw) if field_name == "component": # just keep the keyword change added = removed = "" elif field_name == "target_milestone": field_name = "milestone" if added in IGNORE_MILESTONES: added = "" if removed in IGNORE_MILESTONES: removed = "" ticketChange = {} ticketChange['ticket'] = bugid ticketChange['time'] = activity['bug_when'] ticketChange['author'] = trac.getLoginName(mysql_cur, activity['who']) ticketChange['field'] = field_name ticketChange['oldvalue'] = removed ticketChange['newvalue'] = added if add_keywords or remove_keywords: # ensure removed ones are in old old_keywords = keywords + [kw for kw in remove_keywords if kw not in keywords] # remove from new keywords = [kw for kw in keywords if kw not in remove_keywords] # add to new keywords += [kw for kw in add_keywords if kw not in keywords] if old_keywords != keywords: ticketChangeKw = ticketChange.copy() ticketChangeKw['field'] = "keywords" ticketChangeKw['oldvalue'] = ' '.join(old_keywords) ticketChangeKw['newvalue'] = ' '.join(keywords) ticketChanges.append(ticketChangeKw) if field_name in IGNORED_ACTIVITY_FIELDS: continue # Skip changes that have no effect (think translation!). if added == removed: continue # Bugzilla splits large summary changes into two records. for oldChange in ticketChanges: if (field_name == "summary" and oldChange['field'] == ticketChange['field'] and oldChange['time'] == ticketChange['time'] and oldChange['author'] == ticketChange['author']): oldChange['oldvalue'] += " " + ticketChange['oldvalue'] oldChange['newvalue'] += " " + ticketChange['newvalue'] break # cc and attachments.isobsolete sometime appear # in different activities with same time if ((field_name == "cc" or field_name == "attachments.isobsolete") \ and oldChange['time'] == ticketChange['time']): oldChange['newvalue'] += ", " + ticketChange['newvalue'] break else: ticketChanges.append (ticketChange) for ticketChange in ticketChanges: trac.addTicketChange (**ticketChange) # For some reason, bugzilla v2.11 seems to clear the resolution # when you mark a bug as closed. Let's remember it and restore # it if the ticket is closed but there's no resolution. if not ticket['resolution'] and ticket['status'] == "closed": ticket['resolution'] = resolution bug_status = bug['bug_status'] if bug_status in STATUS_KEYWORDS: kw = STATUS_KEYWORDS[bug_status] if kw not in keywords: keywords.append(kw) product = bug['product'] if product in KEYWORDS_MAPPING and not COMPONENTS_FROM_PRODUCTS: kw = KEYWORDS_MAPPING.get(product, product) if kw and kw not in keywords: keywords.append(kw) component = bug['component'] if (COMPONENTS_FROM_PRODUCTS and \ (MAP_ALL_KEYWORDS or component in KEYWORDS_MAPPING)): kw = KEYWORDS_MAPPING.get(component, component) if kw and kw not in keywords: keywords.append(kw) ticket['keywords'] = string.join(keywords) ticketid = trac.addTicket(**ticket) if BZ_VERSION >= 2210: mysql_cur.execute("SELECT attachments.*, attach_data.thedata " "FROM attachments, attach_data " "WHERE attachments.bug_id = %s AND " "attachments.attach_id = attach_data.id" % bugid) else: mysql_cur.execute("SELECT * FROM attachments WHERE bug_id = %s" % bugid) attachments = mysql_cur.fetchall() for a in attachments: author = trac.getLoginName(mysql_cur, a['submitter_id']) trac.addAttachment(author, a) print "\n8. Importing users and passwords..." if BZ_VERSION >= 2164: selectlogins = "SELECT login_name, cryptpassword FROM profiles"; if IGNORE_DISABLED_USERS: selectlogins = selectlogins + " WHERE disabledtext=''" mysql_cur.execute(selectlogins) users = mysql_cur.fetchall() else: users = () with open('htpasswd', 'w') as f: for user in users: if user['login_name'] in LOGIN_MAP: login = LOGIN_MAP[user['login_name']] else: login = user['login_name'] f.write(login + ':' + user['cryptpassword'] + '\n') print " Bugzilla users converted to htpasswd format, see 'htpasswd'." print "\nAll tickets converted." def log(msg): print "DEBUG: %s" % (msg) def datetime2epoch(dt) : import time return time.mktime(dt.timetuple()) * 1000000 def usage(): print """bugzilla2trac - Imports a bug database from Bugzilla into Trac. Usage: bugzilla2trac.py [options] Available Options: --db - Bugzilla's database name --tracenv /path/to/trac/env - Full path to Trac db environment -h | --host - Bugzilla's DNS host name -u | --user - Effective Bugzilla's database user -p | --passwd - Bugzilla's user password -c | --clean - Remove current Trac tickets before importing -n | --noseverities - import Bugzilla severities as Trac priorities and forget Bugzilla priorities --help | help - This help info Additional configuration options can be defined directly in the script. """ sys.exit(0) def main(): global BZ_DB, BZ_HOST, BZ_USER, BZ_PASSWORD, TRAC_ENV, TRAC_CLEAN global SEVERITIES, PRIORITIES, PRIORITIES_MAP if len (sys.argv) > 1: if sys.argv[1] in ['--help','help'] or len(sys.argv) < 4: usage() iter = 1 while iter < len(sys.argv): if sys.argv[iter] in ['--db'] and iter+1 < len(sys.argv): BZ_DB = sys.argv[iter+1] iter = iter + 1 elif sys.argv[iter] in ['-h', '--host'] and iter+1 < len(sys.argv): BZ_HOST = sys.argv[iter+1] iter = iter + 1 elif sys.argv[iter] in ['-u', '--user'] and iter+1 < len(sys.argv): BZ_USER = sys.argv[iter+1] iter = iter + 1 elif sys.argv[iter] in ['-p', '--passwd'] and iter+1 < len(sys.argv): BZ_PASSWORD = sys.argv[iter+1] iter = iter + 1 elif sys.argv[iter] in ['--tracenv'] and iter+1 < len(sys.argv): TRAC_ENV = sys.argv[iter+1] iter = iter + 1 elif sys.argv[iter] in ['-c', '--clean']: TRAC_CLEAN = 1 elif sys.argv[iter] in ['-n', '--noseverities']: # treat Bugzilla severites as Trac priorities PRIORITIES = SEVERITIES SEVERITIES = [] PRIORITIES_MAP = {} else: print "Error: unknown parameter: " + sys.argv[iter] sys.exit(0) iter = iter + 1 convert(BZ_DB, BZ_HOST, BZ_USER, BZ_PASSWORD, TRAC_ENV, TRAC_CLEAN) if __name__ == '__main__': main() trac-1.0.9+dfsg/contrib/emailfilter.py0000644000175000017500000000451412574541640016050 0ustar wmbwmb#!/usr/bin/python # -*- coding: utf-8 -*- # # Copyright (C) 2005-2013 Edgewall Software # Copyright (C) 2005 Daniel Lundin # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.com/license.html. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://trac.edgewall.org/. """ emailfilter.py -- Email tickets to Trac. A simple MTA filter to create Trac tickets from inbound emails. Copyright 2005, Daniel Lundin Copyright 2005, Edgewall Software Please note: This is only a starting point. See * http://trac.edgewall.org/ticket/5327 and * http://trac-hacks.org/intertrac/EmailtoTracScript for more complete and advanced examples. The scripts reads emails from stdin and inserts directly into a Trac database. MIME headers are mapped as follows: * From: => Reporter * Subject: => Summary * Body => Description How to use ---------- * Set TRAC_ENV_PATH to the path of your project's Trac environment * Configure script as a mail (pipe) filter with your MTA typically, this involves adding a line like this to /etc/aliases: somename: |/path/to/email2trac.py Check your MTA's documentation for specifics. Todo ---- * Configure target database through env variable? * Handle/discard HTML parts * Attachment support """ TRAC_ENV_PATH = '/var/trac/test' import email import sys from trac.env import Environment from trac.ticket import Ticket class TicketEmailParser(object): env = None def __init__(self, env): self.env = env def parse(self, fp): msg = email.message_from_file(fp) tkt = Ticket(self.env) tkt['status'] = 'new' tkt['reporter'] = msg['from'] tkt['summary'] = msg['subject'] for part in msg.walk(): if part.get_content_type() == 'text/plain': tkt['description'] = part.get_payload(decode=1).strip() if tkt.values.get('description'): tkt.insert() if __name__ == '__main__': env = Environment(TRAC_ENV_PATH, create=0) tktparser = TicketEmailParser(env) tktparser.parse(sys.stdin) trac-1.0.9+dfsg/messages-js.cfg0000644000175000017500000000035012574541635014441 0ustar wmbwmb# mapping file for extracting messages from javascript files into # trac/locale/messages-js.pot (see setup.cfg) [javascript: **.js] [extractors] javascript_script = trac.dist:extract_javascript_script [javascript_script: **.html] trac-1.0.9+dfsg/.tx/0000755000175000017500000000000012574541731012247 5ustar wmbwmbtrac-1.0.9+dfsg/.tx/config0000644000175000017500000000075412574541634013447 0ustar wmbwmb[main] host = https://www.transifex.com [trac.1_0-stable-messages-js-pot] file_filter = trac/locale//LC_MESSAGES/messages-js.po source_file = trac/locale/messages-js.pot source_lang = en [trac.1_0-stable-messages-pot] file_filter = trac/locale//LC_MESSAGES/messages.po source_file = trac/locale/messages.pot source_lang = en [trac.1_0-stable-tracini-pot] file_filter = trac/locale//LC_MESSAGES/tracini.po source_file = trac/locale/tracini.pot source_lang = en type = PO trac-1.0.9+dfsg/COPYING0000644000175000017500000000261112574541634012573 0ustar wmbwmbCopyright (C) 2003-2015 Edgewall Software All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. trac-1.0.9+dfsg/README0000644000175000017500000000164612574541640012424 0ustar wmbwmbAbout Trac ========== Trac is a minimalistic web-based software project management and bug/issue tracking system. It provides an interface to the Git and Subversion revision control systems, an integrated wiki, flexible issue tracking and convenient report facilities. Trac is distributed using the modified BSD License. * For installation instructions, please see the INSTALL for a short overview and the trac/wiki/default-pages/TracInstall page for complete, up-to-date instructions * If you are upgrading from a previous Trac version, please read UPGRADE for a short overview and the trac/wiki/default-pages/TracUpgrade for complete, up-to-date instructions You might also want to take a look at the RELEASE and ChangeLog files for more information about this release. Otherwise, the primary source of information is the main Trac web site: We hope you enjoy it, /The Trac Team trac-1.0.9+dfsg/setup.cfg0000644000175000017500000000373112574541731013363 0ustar wmbwmb[bdist_wininst] bitmap = setup_wininst.bmp [extract_messages] add_comments = TRANSLATOR: copyright_holder = Edgewall Software msgid_bugs_address = trac-dev@googlegroups.com output_file = trac/locale/messages.pot keywords = _ ngettext:1,2 N_ tag_ tagn_:1,2 cleandoc_ [init_catalog] input_file = trac/locale/messages.pot output_dir = trac/locale [compile_catalog] directory = trac/locale [update_catalog] input_file = trac/locale/messages.pot output_dir = trac/locale [extract_messages_js] add_comments = TRANSLATOR: copyright_holder = Edgewall Software msgid_bugs_address = trac-dev@googlegroups.com output_file = trac/locale/messages-js.pot keywords = _ ngettext:1,2 N_ mapping_file = messages-js.cfg [init_catalog_js] domain = messages-js input_file = trac/locale/messages-js.pot output_dir = trac/locale [compile_catalog_js] domain = messages-js directory = trac/locale [update_catalog_js] domain = messages-js input_file = trac/locale/messages-js.pot output_dir = trac/locale [generate_messages_js] domain = messages-js input_dir = trac/locale output_dir = trac/htdocs/js/messages [extract_messages_tracini] add_comments = TRANSLATOR: copyright_holder = Edgewall Software msgid_bugs_address = trac-dev@googlegroups.com output_file = trac/locale/tracini.pot keywords = ConfigSection:2 Option:4 BoolOption:4 IntOption:4 FloatOption:4 ListOption:6 ChoiceOption:4 PathOption:4 ExtensionOption:5 OrderedExtensionsOption:6 no-default-keywords = yes mapping_file = tracini.cfg [init_catalog_tracini] domain = tracini input_file = trac/locale/tracini.pot output_dir = trac/locale [compile_catalog_tracini] domain = tracini directory = trac/locale [update_catalog_tracini] domain = tracini input_file = trac/locale/tracini.pot output_dir = trac/locale [check_catalog] domain = messages input_dir = trac/locale [check_catalog_js] domain = messages-js input_dir = trac/locale [check_catalog_tracini] domain = tracini input_dir = trac/locale [egg_info] tag_build = tag_date = 0 tag_svn_revision = 0 trac-1.0.9+dfsg/INSTALL0000644000175000017500000007163012574541640012575 0ustar wmbwmbTrac Installation Guide for 1.0 =============================== Trac is written in the Python programming language and needs a database, `SQLite`_, `PostgreSQL`_, or `MySQL`_. For HTML rendering, Trac uses the `Genshi`_ templating system. Since version 0.12, Trac can also be localized, and there is probably a translation available in your language. If you want to use the Trac interface in other languages, then make sure you have installed the optional package `Babel`_. Pay attention to the extra steps for localization support in the `Installing Trac`_ section below. Lacking Babel, you will only get the default English version. If you're interested in contributing new translations for other languages or enhancing the existing translations, then please have a look at `TracL10N`_. What follows are generic instructions for installing and setting up Trac. While you may find instructions for installing Trac on specific systems at `TracInstallPlatforms`_ on the main Trac site, please first read through these general instructions to get a good understanding of the tasks involved. Installation Steps `````````````````` #. Dependencies #. Mandatory Dependencies #. Optional Dependencies #. Installing Trac #. Using easy_install #. Using pip #. From source #. Using installer #. Using package manager #. Advanced easy_install Options #. Creating a Project Environment #. Deploying Trac #. Running the Standalone Server #. Running Trac on a Web Server #. Configuring Authentication #. Granting admin rights to the admin user #. Finishing the install #. Enable version control components #. Using Trac Dependencies ------------ Mandatory Dependencies ~~~~~~~~~~~~~~~~~~~~~~ To install Trac, the following software packages must be installed: + `Python`_, version >= 2.5 and < 3.0 (note that we dropped the support for Python 2.4 in this release) + `setuptools`_, version >= 0.6 + `Genshi*`_, version >= 0.6 You also need a database system and the corresponding python bindings. The database can be either SQLite, PostgreSQL or MySQL. For the SQLite database ``````````````````````` As you must be using Python 2.5, 2.6 or 2.7, you already have the SQLite database bindings bundled with the standard distribution of Python: the sqlite3 module. Optionally, you may install a newer version of `pysqlite`_ than the one provided by the Python distribution. See `PySqlite*`_ for details. For the PostgreSQL database ``````````````````````````` You need to install the database and its Python bindings: + `PostgreSQL`_, version 8.0 or later + `psycopg2`_, version 2.0 or later See `DatabaseBackend`_ for details. For the MySQL database `````````````````````` Trac works well with MySQL, provided you follow the guidelines: + `MySQL`_ or `MariaDB`_, version 5.0 or later + `MySQLdb`_, version 1.2.2 or later Given the caveats and known issues surrounding MySQL, read the `MySqlDb*`_ page before creating the database. Optional Dependencies ~~~~~~~~~~~~~~~~~~~~~ Version Control System `````````````````````` Subversion ++++++++++ + `Subversion`_, 1.5.x or later and the corresponding Python bindings. Older versions starting from 1.0, like 1.2.4, 1.3.2 or 1.4.2, etc. may still work. For troubleshooting information, check the `TracSubversion`_ page. There are `pre-compiled SWIG bindings`_ available for various platforms. (Good luck finding precompiled SWIG bindings for any Windows package at that listing. `TracSubversion*`_ points you to `Alagazam`_, which works for me under Python 2.6.) Note that Trac doesn't use `PySVN`_, neither does it work yet with the newer ctype -style bindings. Note: if using Subversion, Trac must be installed on the same machine . Remote repositories are currently `not supported`_. Git +++ + `Git`_ 1.5.6 or later. More information is available on the `TracGit`_ page. Others ++++++ Support for other version control systems is provided via third- parties. See `PluginList#VersionControlSystems`_ and `VersionControlSystem`_. Web Server `````````` A web server is optional because Trac has a server included, see the `Running the Standalone Server`_ section below. Alternatively you can configure Trac to run in any of the following environments: + `Apache`_ with + `mod_wsgi`_, see `TracModWSGI`_ and `http://code.google.com/p/modwsgi/wiki/IntegrationWithTrac`_ + `mod_python 3.5.0`_, see `TracModPython`_ + a `FastCGI`_-capable web server (see `TracFastCgi`_) + an `AJP`_-capable web server (see `TracOnWindowsIisAjp`_) + a FastCGI and FastCGI-to-WSGI gateway (see `TracOnWindowsIisWfastcgi`_) + a CGI-capable web server (see `TracCgi`_), but *usage of Trac as a cgi script is highly discouraged*, better use one of the previous options. Other Python Packages ````````````````````` + `Babel*`_, version 0.9.5, 0.9.6 or >= 1.3 needed for localization support + `docutils`_, version >= 0.3.9 for `WikiRestructuredText`_. + `Pygments`_ for `syntax highlighting`_.`SilverCity`_ and/or `Enscript`_ may still be used but are deprecated and you really should be using Pygments. + `pytz`_ to get a complete list of time zones, otherwise Trac will fall back on a shorter list from an internal time zone implementation. Attention : The available versions of these dependencies are not necessarily interchangeable, so please pay attention to the version numbers. If you are having trouble getting Trac to work, please double-check all the dependencies before asking for help on the `MailingList`_ or `IrcChannel`_. Please refer to the documentation of these packages to find out how they are best installed. In addition, most of the `platform-specific instructions`_ also describe the installation of the dependencies. Keep in mind however that the information there probably concern older versions of Trac than the one you're installing. Installing Trac --------------- The `trac-admin`_ command-line tool, used to create and maintain `project environments`_, as well as the `tracd`_ standalone server are installed along with Trac. There are several methods for installing Trac. It is assumed throughout this guide that you have elevated permissions as the root user, or by prefixing commands with sudo . The umask 0022 should be used for a typical installation on a Unix-based platform. Using easy_install ~~~~~~~~~~~~~~~~~~ Trac can be installed from `PyPI`_ or the Subversion repository using `setuptools`_. A few command-line examples: + Install Trac 1.0: :: $ easy_install Trac==1.0 + Install latest development version: :: $ easy_install Trac==dev Note that in this case you won't have the possibility to run a localized version of Trac; either use a released version or install from source. More information can be found on the `setuptools*`_ page. Setuptools Warning: If the version of your setuptools is in the range 5.4 through 5.6, the environment variable PKG_RESOURCES_CACHE_ZIP_MANIFESTS must be set in order to avoid significant performance degradation. More information may be found in the sections on `Running The Standalone Server`_ and `Running Trac on a Web Server`_. Using pip ~~~~~~~~~ 'pip' is an easy_install replacement that is very useful to quickly install Python packages. To get a Trac installation up and running in less than 5 minutes: Assuming you want to have your entire pip installation in /opt/user/trac : + :: $ pip install trac psycopg2 or: + :: $ pip install trac mysql-python Make sure your OS specific headers are available for pip to automatically build PostgreSQL ( libpq-dev ) or MySQL ( libmysqlclient-dev ) bindings. pip will automatically resolve all dependencies (like Genshi, pygments, etc.), download the latest packages from pypi.python.org and create a self contained installation in /opt/user/trac . All commands ( tracd , trac-admin ) are available in /opt/user/trac/bin . This can also be leveraged for mod_python (using PythonHandler directive) and mod_wsgi (using WSGIDaemonProcess directive). Additionally, you can install several Trac plugins (listed `here`_) through pip. From source ~~~~~~~~~~~ Using the python-typical setup at the top of the source directory also works. You can obtain the source for a .tar.gz or .zip file corresponding to a release (e.g. Trac-1.0.tar.gz ) from the `TracDownload`_ page, or you can get the source directly from the repository. See `TracRepositories`_ for details. :: $ python ./setup.py install You will need root permissions or equivalent for this step. This will byte-compile the Python source code and install it as an .egg file or folder in the site-packages directory of your Python installation. The .egg will also contain all other resources needed by standard Trac, such as htdocs and templates . If you install from source and want to make Trac available in other languages, make sure Babel is installed. Only then, perform the install (or simply redo the install once again afterwards if you realize Babel was not yet installed): :: $ python ./setup.py install Alternatively, you can run bdist_egg and copy the .egg from dist/ to the place of your choice, or you can create a Windows installer ( bdist_wininst ). Using installer ~~~~~~~~~~~~~~~ On Windows Trac can be installed using the exe installers available on the `TracDownload`_ page. Installers are available for the 32 and 64 bit versions of Python. Make sure to use the installer that matches the architecture of your Python installation. Using package manager ~~~~~~~~~~~~~~~~~~~~~ Trac may be available in the package repository for your platform. Note however, that the version provided by the package manager may not be the latest release. Advanced easy_install Options ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ To install Trac to a custom location, or find out about other advanced installation options, run: :: $ easy_install --help Also see `Installing Python Modules`_ for detailed information. Specifically, you might be interested in: :: $ easy_install --prefix=/path/to/installdir or, if installing Trac on a Mac OS X system: :: $ easy_install --prefix=/usr/local --install-dir=/Library/Python/2.5/site-packages Note : If installing on Mac OS X 10.6 running easy_install http://svn.edgewall.org/repos/trac/trunk will install into /usr/local and /Library/Python/2.5/site-packages by default. The above will place your tracd and trac-admin commands into /usr/local/bin and will install the Trac libraries and dependencies into /Library/Python/2.5/site-packages , which is Apple's preferred location for third-party Python application installations. Creating a Project Environment ------------------------------ A `Trac environment`_ is the backend where Trac stores information like wiki pages, tickets, reports, settings, etc. An environment is basically a directory that contains a human-readable `configuration file`_, and other files and directories. A new environment is created using `trac-admin`_: :: $ trac-admin /path/to/myproject initenv `trac-admin`_ will prompt you for the information it needs to create the environment, such as the name of the project and the `database connection string`_. If you're not sure what to specify for one of these options, just press to use the default value. Using the default database connection string in particular will always work as long as you have SQLite installed. For the other `database backends`_ you should plan ahead and already have a database ready to use at this point. Since 0.12, Trac doesn't ask for a `source code repository`_ anymore when creating an environment. Repositories can be `added`_ afterwards, and support for specific version control systems is disabled by default. Also note that the values you specify here can be changed later by directly editing the `conf/trac.ini`_ configuration file. Filesystem Warning: When selecting the location of your environment, make sure that the filesystem on which the environment directory resides supports sub-second timestamps (i.e. not ext2 or ext3 on Linux, or HFS+ on OSX), as the modification time of the conf/trac.ini file will be monitored to decide whether an environment restart is needed or not. A too coarse-grained timestamp resolution may result in inconsistencies in Trac < 1.0.2. The best advice is to opt for a platform with sub-second timestamp resolution, regardless of the Trac version. Finally, make sure the user account under which the web front-end runs will have write permissions to the environment directory and all the files inside. This will be the case if you run trac-admin ... initenv as this user. If not, you should set the correct user afterwards. For example on Linux, with the web server running as user apache and group apache , enter: :: $ chown -R apache.apache /path/to/myproject The actual username and groupname of the Apache server may not be exactly apache , and are specified in the Apache configuration file by the directives User and Group (if Apache httpd is what you use). Warning: Please only use ASCII-characters for account name and project path, unicode characters are not supported there. Deploying Trac -------------- Running the Standalone Server ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ After having created a Trac environment, you can easily try the web interface by running the standalone server `tracd`_: :: $ tracd --port 8000 /path/to/myproject Then, fire up a browser and visit http://localhost:8000/ . You should get a simple listing of all environments that tracd knows about. Follow the link to the environment you just created, and you should see Trac in action. If you only plan on managing a single project with Trac you can have the standalone server skip the environment list by starting it like this: :: $ tracd -s --port 8000 /path/to/myproject Setuptools Warning: If the version of your setuptools is in the range 5.4 through 5.6, the environment variable PKG_RESOURCES_CACHE_ZIP_MANIFESTS must be set in order to avoid significant performance degradation. The environment variable can be set system-wide, or for just the user that runs the tracd process. There are several ways to accomplish this in addition to what is discussed here, and depending on the distribution of your OS. To be effective system-wide a shell script with the export statement may be added to /etc/profile.d . Ubuntu/Debian users can add the export statement to /etc/apache2/envvars . RedHat/CentOS/Fedora users can add the export statement to /etc/sysconfig/httpd . To be effective for a user session the export statement may be added to ~/.profile . :: export PKG_RESOURCES_CACHE_ZIP_MANIFESTS=1 Alternatively, the variable can be set in the shell before executing tracd : :: $ PKG_RESOURCES_CACHE_ZIP_MANIFESTS=1 tracd --port 8000 /path/to/myproject Running Trac on a Web Server ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Trac provides various options for connecting to a "real" web server: + `FastCGI*`_ + `mod_wsgi*`_ + `mod_python`_ + *`CGI`_: should not be used, as it degrades performance* Trac also supports `AJP*`_ which may be your choice if you want to connect to IIS. Other deployment scenarios are possible: `nginx`_, `uwsgi`_, `Isapi-wsgi`_ etc. Generating the Trac cgi-bin directory ````````````````````````````````````` In order for Trac to function properly with FastCGI you need to have a trac.fcgi file and for mod_wsgi a trac.wsgi file. These are Python scripts which load the appropriate Python code. They can be generated using the deploy option of `trac-admin`_. There is, however, a bit of a chicken-and-egg problem. The `trac- admin`_ command requires an existing environment to function, but complains if the deploy directory already exists. This is a problem, because environments are often stored in a subdirectory of the deploy. The solution is to do something like this: :: $ mkdir -p /usr/share/trac/projects/my-project $ trac-admin /usr/share/trac/projects/my-project initenv $ trac-admin /usr/share/trac/projects/my-project deploy /tmp/deploy $ mv /tmp/deploy/* /usr/share/trac Don't forget to check that the web server has the execution right on scripts in the /usr/share/trac/cgi-bin directory. Mapping Static Resources ```````````````````````` Out of the box, Trac will pass static resources such as style sheets or images through itself. For anything but a tracd only based deployment, this is far from optimal as the web server could be set up to directly serve those static resources. For CGI setup, this is highly undesirable as it causes abysmal performance. Web servers such as `Apache`_ allow you to create "Aliases" to resources, giving them a virtual URL that doesn't necessarily reflect the layout of the servers file system. We also can map requests for static resources directly to the directory on the file system, avoiding processing these requests by Trac itself. There are two primary URL paths for static resources - /chrome/common and /chrome/site . Plugins can add their own resources, usually accessible by /chrome/ path, so its important to override only known paths and not try to make universal /chrome alias for everything. Note that in order to get those static resources on the filesystem, you need first to extract the relevant resources from Trac using the `trac-admin`_ deploy command: :: deploy Extract static resources from Trac and all plugins The target will then contain an htdocs directory with: + site/ - a copy of the environment's directory htdocs/ + common/ - the static resources of Trac itself + / - one directory for each resource directory managed by the plugins enabled for this environment Example: Apache and ScriptAlias +++++++++++++++++++++++++++++++ Assuming the deployment has been done this way: :: $ trac-admin /var/trac/env deploy /path/to/shared/trac Add the following snippet to Apache configuration *before* the ScriptAlias or WSGIScriptAlias (which map all the other requests to the Trac application), changing paths to match your deployment: :: Alias /trac/chrome/common /path/to/trac/htdocs/common Alias /trac/chrome/site /path/to/trac/htdocs/site Order allow,deny Allow from all If using mod_python, you might want to add this too, otherwise the alias will be ignored: :: SetHandler None Note that we mapped /trac part of the URL to the trac.*cgi script, and the path /trac/chrome/common is the path you have to append to that location to intercept requests to the static resources. Similarly, if you have static resources in a project's htdocs directory (which is referenced by /trac/chrome/site URL in themes), you can configure Apache to serve those resources (again, put this *before* the ScriptAlias or WSGIScriptAlias for the .*cgi scripts, and adjust names and locations to match your installation): :: Alias /trac/chrome/site /path/to/projectenv/htdocs Order allow,deny Allow from all Alternatively to aliasing /trac/chrome/common , you can tell Trac to generate direct links for those static resources (and only those), using the ` [trac] htdocs_location`_ configuration setting: :: [trac] htdocs_location = http://static.example.org/trac-common/ Note that this makes it easy to have a dedicated domain serve those static resources, preferentially `cookie-less`_. Of course, you still need to make the Trac htdocs/common directory available through the web server at the specified URL, for example by copying (or linking) the directory into the document root of the web server: :: $ ln -s /path/to/trac/htdocs/common /var/www/static.example.org/trac-common Setting up the Plugin Cache ``````````````````````````` Some Python plugins need to be extracted to a cache directory. By default the cache resides in the home directory of the current user. When running Trac on a Web Server as a dedicated user (which is highly recommended) who has no home directory, this might prevent the plugins from starting. To override the cache location you can set the PYTHON_EGG_CACHE environment variable. Refer to your server documentation for detailed instructions on how to set environment variables. Configuring Authentication -------------------------- Trac uses HTTP authentication. You'll need to configure your webserver to request authentication when the .../login URL is hit (the virtual path of the "login" button). Trac will automatically pick the REMOTE_USER variable up after you provide your credentials. Therefore, all user management goes through your web server configuration. Please consult the documentation of your web server for more info. The process of adding, removing, and configuring user accounts for authentication depends on the specific way you run Trac. Please refer to one of the following sections: + `TracStandalone#UsingAuthentication`_ if you use the standalone server, tracd . + `TracModWSGI#ConfiguringAuthentication`_ if you use the Apache web server, with any of its front end: mod_wsgi of course, but the same instructions applies also for mod_python , mod_fcgi or mod_fastcgi . + `TracFastCgi`_ if you are using another web server with FCGI support, such as Cherokee, Lighttpd, LiteSpeed, nginx. The following document also contains some useful information for beginners: `TracAuthenticationIntroduction`_. Granting admin rights to the admin user --------------------------------------- Grant admin rights to user admin: :: $ trac-admin /path/to/myproject permission add admin TRAC_ADMIN This user will have an "Admin" entry menu that will allow you to administrate your Trac project. Finishing the install --------------------- Enable version control components ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Support for version control systems is provided by optional components in Trac and the components are disabled by default *(since 1.0)*. Subversion and Git must be explicitly enabled if you wish to use them. See `TracRepositoryAdmin`_ for more details. The version control systems are enabled by adding the following to the [components] section of your `trac.ini`_, or enabling the components in the "Plugins" admin panel: :: [components] tracopt.versioncontrol.svn.* = enabled :: [components] tracopt.versioncontrol.git.* = enabled After enabling the components, repositories can be configured through the *Repositories* admin panel or by editing `trac.ini*`_. Automatic changeset references can be inserted as ticket comments by configuring `CommitTicketUpdater`_. Using Trac ~~~~~~~~~~ Once you have your Trac site up and running, you should be able to create tickets, view the timeline, browse your version control repository if configured, etc. Keep in mind that *anonymous* (not logged in) users can by default access only a few of the features, in particular they will have a read-only access to the resources. You will need to configure authentication and grant additional `permissions`_ to authenticated users to see the full set of features. *Enjoy!* `The Trac Team`_ See also: `TracInstallPlatforms`_, `TracGuide`_, `TracUpgrade`_, `TracPermissions`_ .. _ [trac] htdocs_location: http://trac.edgewall.org/wiki/TracIni#trac-section .. _added: http://trac.edgewall.org/wiki/TracRepositoryAdmin .. _AJP*: http://trac.edgewall.org/intertrac/TracOnWindowsIisAjp .. _AJP: http://tomcat.apache.org/connectors-doc/ajp/ajpv13a.html .. _Alagazam: http://alagazam.net .. _Apache: http://httpd.apache.org/ .. _Babel*: http://babel.edgewall.org .. _Babel: http://trac.edgewall.org/wiki/TracInstall#OtherPythonPackages .. _CGI: http://trac.edgewall.org/wiki/TracCgi .. _CommitTicketUpdater: http://trac.edgewall.org/wiki/TracRepositoryAdmin#Automaticchangesetreferencesintickets .. _conf/trac.ini: http://trac.edgewall.org/wiki/TracIni .. _configuration file: http://trac.edgewall.org/wiki/TracIni .. _cookie-less: http://code.google.com/speed/page-speed/docs/request.html#ServeFromCookielessDomain .. _database backends: http://trac.edgewall.org/intertrac/DatabaseBackend .. _database connection string: http://trac.edgewall.org/wiki/TracEnvironment#DatabaseConnectionStrings .. _DatabaseBackend: http://trac.edgewall.org/intertrac/DatabaseBackend%23Postgresql .. _docutils: http://docutils.sourceforge.net/ .. _Enscript: http://gnu.org/software/enscript/enscript.html .. _FastCGI*: http://trac.edgewall.org/wiki/TracFastCgi .. _FastCGI: http://www.fastcgi.com/ .. _Genshi*: http://genshi.edgewall.org/wiki/Download .. _Genshi: http://genshi.edgewall.org .. _Git: http://git-scm.com/ .. _here: https://pypi.python.org/pypi?:action=browse&show=all&c=516 .. _http://code.google.com/p/modwsgi/wiki/IntegrationWithTrac: http://code.google.com/p/modwsgi/wiki/IntegrationWithTrac .. _Installing Python Modules: http://docs.python.org/2/install/index.html .. _Installing Trac: http://trac.edgewall.org/wiki/TracInstall#InstallingTrac .. _IrcChannel: http://trac.edgewall.org/intertrac/IrcChannel .. _Isapi-wsgi: http://trac.edgewall.org/intertrac/TracOnWindowsIisIsapi .. _MailingList: http://trac.edgewall.org/intertrac/MailingList .. _MariaDB: http://mariadb.org/ .. _mod_python 3.5.0: http://modpython.org/ .. _mod_python: http://trac.edgewall.org/wiki/TracModPython .. _mod_wsgi*: http://trac.edgewall.org/wiki/TracModWSGI .. _mod_wsgi: http://code.google.com/p/modwsgi/ .. _MySQL: http://mysql.com/ .. _MySqlDb*: http://trac.edgewall.org/intertrac/MySqlDb .. _MySQLdb: http://sf.net/projects/mysql-python .. _nginx: http://trac.edgewall.org/intertrac/TracNginxRecipe .. _not supported: http://trac.edgewall.org/intertrac/ticket%3A493 .. _permissions: http://trac.edgewall.org/wiki/TracPermissions .. _platform-specific instructions: http://trac.edgewall.org/intertrac/TracInstallPlatforms .. _PluginList#VersionControlSystems: http://trac.edgewall.org/intertrac/PluginList%23VersionControlSystems .. _PostgreSQL: http://www.postgresql.org/ .. _pre-compiled SWIG bindings: http://subversion.apache.org/packages.html .. _project environments: http://trac.edgewall.org/wiki/TracEnvironment .. _psycopg2: http://pypi.python.org/pypi/psycopg2 .. _Pygments: http://pygments.org .. _PyPI: https://pypi.python.org/pypi/Trac .. _PySqlite*: http://trac.edgewall.org/intertrac/PySqlite%23ThePysqlite2bindings .. _pysqlite: http://pypi.python.org/pypi/pysqlite .. _PySVN: http://pysvn.tigris.org/ .. _Python: http://www.python.org/ .. _pytz: http://pytz.sf.net .. _Running the Standalone Server: http://trac.edgewall.org/wiki/TracInstall#RunningtheStandaloneServer .. _Running Trac on a Web Server: http://trac.edgewall.org/wiki/TracInstall#RunningTraconaWebServer .. _setuptools*: http://trac.edgewall.org/intertrac/wiki%3Asetuptools .. _setuptools: http://pypi.python.org/pypi/setuptools .. _SilverCity: http://silvercity.sourceforge.net/ .. _source code repository: http://trac.edgewall.org/wiki/TracEnvironment#SourceCodeRepository .. _SQLite: http://sqlite.org/ .. _Subversion: http://subversion.apache.org/ .. _syntax highlighting: http://trac.edgewall.org/wiki/TracSyntaxColoring .. _The Trac Team: http://trac.edgewall.org/intertrac/TracTeam .. _Trac environment: http://trac.edgewall.org/wiki/TracEnvironment .. _trac-admin: http://trac.edgewall.org/wiki/TracAdmin .. _trac.ini*: http://trac.edgewall.org/wiki/TracIni#repositories-section .. _trac.ini: http://trac.edgewall.org/wiki/TracIni#components-section .. _TracAuthenticationIntroduction: http://trac.edgewall.org/intertrac/TracAuthenticationIntroduction .. _TracCgi: http://trac.edgewall.org/wiki/TracCgi .. _tracd: http://trac.edgewall.org/wiki/TracStandalone .. _TracDownload: http://trac.edgewall.org/intertrac/TracDownload .. _TracFastCgi: http://trac.edgewall.org/wiki/TracFastCgi .. _TracGit: http://trac.edgewall.org/intertrac/TracGit .. _TracGuide: http://trac.edgewall.org/wiki/TracGuide .. _TracInstallPlatforms: http://trac.edgewall.org/intertrac/TracInstallPlatforms .. _TracL10N: http://trac.edgewall.org/intertrac/wiki%3ATracL10N .. _TracModPython: http://trac.edgewall.org/wiki/TracModPython .. _TracModWSGI#ConfiguringAuthentication: http://trac.edgewall.org/wiki/TracModWSGI#ConfiguringAuthentication .. _TracModWSGI: http://trac.edgewall.org/wiki/TracModWSGI .. _TracOnWindowsIisAjp: http://trac.edgewall.org/intertrac/TracOnWindowsIisAjp .. _TracOnWindowsIisWfastcgi: http://trac.edgewall.org/intertrac/TracOnWindowsIisWfastcgi .. _TracPermissions: http://trac.edgewall.org/wiki/TracPermissions .. _TracRepositories: http://trac.edgewall.org/intertrac/TracRepositories%23OfficialSubversionrepository .. _TracRepositoryAdmin: http://trac.edgewall.org/wiki/TracRepositoryAdmin .. _TracStandalone#UsingAuthentication: http://trac.edgewall.org/wiki/TracStandalone#UsingAuthentication .. _TracSubversion*: http://trac.edgewall.org/intertrac/TracSubversion .. _TracSubversion: http://trac.edgewall.org/intertrac/TracSubversion%23Troubleshooting .. _TracUpgrade: http://trac.edgewall.org/wiki/TracUpgrade .. _uwsgi: http://projects.unbit.it/uwsgi/wiki/Example#Traconapacheinasub-uri .. _VersionControlSystem: http://trac.edgewall.org/intertrac/VersionControlSystem .. _WikiRestructuredText: http://trac.edgewall.org/wiki/WikiRestructuredText trac-1.0.9+dfsg/trac/0000755000175000017500000000000012574541731012467 5ustar wmbwmbtrac-1.0.9+dfsg/trac/core.py0000644000175000017500000002042012574541640013766 0ustar wmbwmb# -*- coding: utf-8 -*- # # Copyright (C) 2003-2011 Edgewall Software # Copyright (C) 2003-2004 Jonas Borgström # Copyright (C) 2004-2005 Christopher Lenz # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.org/wiki/TracLicense. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://trac.edgewall.org/log/. # # Author: Jonas Borgström # Christopher Lenz __all__ = ['Component', 'ExtensionPoint', 'implements', 'Interface', 'TracError'] def N_(string): """No-op translation marker, inlined here to avoid importing from `trac.util`. """ return string class TracError(Exception): """Exception base class for errors in Trac.""" title = N_("Trac Error") def __init__(self, message, title=None, show_traceback=False): """If message is a genshi.builder.tag object, everything up to the first

will be displayed in the red box, and everything after will be displayed below the red box. If title is given, it will be displayed as the large header above the error message. """ from trac.util.translation import gettext super(TracError, self).__init__(message) self._message = message self.title = title or gettext(self.title) self.show_traceback = show_traceback message = property(lambda self: self._message, lambda self, v: setattr(self, '_message', v)) def __unicode__(self): return unicode(self.message) class Interface(object): """Marker base class for extension point interfaces.""" class ExtensionPoint(property): """Marker class for extension points in components.""" def __init__(self, interface): """Create the extension point. :param interface: the `Interface` subclass that defines the protocol for the extension point """ property.__init__(self, self.extensions) self.interface = interface self.__doc__ = ("List of components that implement `~%s.%s`" % (self.interface.__module__, self.interface.__name__)) def extensions(self, component): """Return a list of components that declare to implement the extension point interface. """ classes = ComponentMeta._registry.get(self.interface, ()) components = [component.compmgr[cls] for cls in classes] return [c for c in components if c] def __repr__(self): """Return a textual representation of the extension point.""" return '' % self.interface.__name__ class ComponentMeta(type): """Meta class for components. Takes care of component and extension point registration. """ _components = [] _registry = {} def __new__(mcs, name, bases, d): """Create the component class.""" new_class = type.__new__(mcs, name, bases, d) if name == 'Component': # Don't put the Component base class in the registry return new_class if d.get('abstract'): # Don't put abstract component classes in the registry return new_class ComponentMeta._components.append(new_class) registry = ComponentMeta._registry for cls in new_class.__mro__: for interface in cls.__dict__.get('_implements', ()): classes = registry.setdefault(interface, []) if new_class not in classes: classes.append(new_class) return new_class def __call__(cls, *args, **kwargs): """Return an existing instance of the component if it has already been activated, otherwise create a new instance. """ # If this component is also the component manager, just invoke that if issubclass(cls, ComponentManager): self = cls.__new__(cls) self.compmgr = self self.__init__(*args, **kwargs) return self # The normal case where the component is not also the component manager assert len(args) >= 1 and isinstance(args[0], ComponentManager), \ "First argument must be a ComponentManager instance" compmgr = args[0] self = compmgr.components.get(cls) # Note that this check is racy, we intentionally don't use a # lock in order to keep things simple and avoid the risk of # deadlocks, as the impact of having temporarily two (or more) # instances for a given `cls` is negligible. if self is None: self = cls.__new__(cls) self.compmgr = compmgr compmgr.component_activated(self) self.__init__() # Only register the instance once it is fully initialized (#9418) compmgr.components[cls] = self return self class Component(object): """Base class for components. Every component can declare what extension points it provides, as well as what extension points of other components it extends. """ __metaclass__ = ComponentMeta @staticmethod def implements(*interfaces): """Can be used in the class definition of `Component` subclasses to declare the extension points that are extended. """ import sys frame = sys._getframe(1) locals_ = frame.f_locals # Some sanity checks assert locals_ is not frame.f_globals and '__module__' in locals_, \ 'implements() can only be used in a class definition' locals_.setdefault('_implements', []).extend(interfaces) implements = Component.implements class ComponentManager(object): """The component manager keeps a pool of active components.""" def __init__(self): """Initialize the component manager.""" self.components = {} self.enabled = {} if isinstance(self, Component): self.components[self.__class__] = self def __contains__(self, cls): """Return whether the given class is in the list of active components.""" return cls in self.components def __getitem__(self, cls): """Activate the component instance for the given class, or return the existing instance if the component has already been activated. Note that `ComponentManager` components can't be activated that way. """ if not self.is_enabled(cls): return None component = self.components.get(cls) if not component and not issubclass(cls, ComponentManager): if cls not in ComponentMeta._components: raise TracError('Component "%s" not registered' % cls.__name__) try: component = cls(self) except TypeError, e: raise TracError("Unable to instantiate component %r (%s)" % (cls, e)) return component def is_enabled(self, cls): """Return whether the given component class is enabled.""" if cls not in self.enabled: self.enabled[cls] = self.is_component_enabled(cls) return self.enabled[cls] def disable_component(self, component): """Force a component to be disabled. :param component: can be a class or an instance. """ if not isinstance(component, type): component = component.__class__ self.enabled[component] = False self.components[component] = None def component_activated(self, component): """Can be overridden by sub-classes so that special initialization for components can be provided. """ def is_component_enabled(self, cls): """Can be overridden by sub-classes to veto the activation of a component. If this method returns `False`, the component was disabled explicitly. If it returns `None`, the component was neither enabled nor disabled explicitly. In both cases, the component with the given class will not be available. """ return True trac-1.0.9+dfsg/trac/prefs/0000755000175000017500000000000012574541731013606 5ustar wmbwmbtrac-1.0.9+dfsg/trac/prefs/web_ui.py0000644000175000017500000001444012574541640015434 0ustar wmbwmb# -*- coding: utf-8 -*- # # Copyright (C) 2004-2014 Edgewall Software # Copyright (C) 2004-2005 Daniel Lundin # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.org/wiki/TracLicense. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://trac.edgewall.org/log/. # # Author: Daniel Lundin import pkg_resources import re from genshi.builder import tag from trac.core import * from trac.prefs.api import IPreferencePanelProvider from trac.util.datefmt import all_timezones, get_timezone, localtz from trac.util.translation import _, Locale, deactivate,\ get_available_locales, make_activable from trac.web.api import HTTPNotFound, IRequestHandler from trac.web.chrome import INavigationContributor, ITemplateProvider, \ add_notice, add_stylesheet class PreferencesModule(Component): panel_providers = ExtensionPoint(IPreferencePanelProvider) implements(INavigationContributor, IPreferencePanelProvider, IRequestHandler, ITemplateProvider) _form_fields = [ 'newsid', 'name', 'email', 'tz', 'lc_time', 'dateinfo', 'language', 'accesskeys', 'ui.use_symbols', 'ui.hide_help', ] # INavigationContributor methods def get_active_navigation_item(self, req): return 'prefs' def get_navigation_items(self, req): yield 'metanav', 'prefs', tag.a(_("Preferences"), href=req.href.prefs()) # IRequestHandler methods def match_request(self, req): match = re.match('/prefs(?:/([^/]+))?$', req.path_info) if match: req.args['panel_id'] = match.group(1) return True def process_request(self, req): xhr = req.get_header('X-Requested-With') == 'XMLHttpRequest' if xhr and req.method == 'POST' and 'save_prefs' in req.args: self._do_save_xhr(req) panel_id = req.args.get('panel_id') panels = [] chosen_provider = None for provider in self.panel_providers: for name, label in provider.get_preference_panels(req) or []: if name == panel_id or None: chosen_provider = provider panels.append((name, label)) if not chosen_provider: raise HTTPNotFound(_("Unknown preference panel")) template, data = chosen_provider.render_preference_panel(req, panel_id) data.update({'active_panel': panel_id, 'panels': panels}) add_stylesheet(req, 'common/css/prefs.css') return template, data, None # IPreferencePanelProvider methods def get_preference_panels(self, req): yield (None, _("General")) yield ('datetime', _("Date & Time")) yield ('keybindings', _("Keyboard Shortcuts")) yield ('userinterface', _("User Interface")) if Locale or 'TRAC_ADMIN' in req.perm: yield ('language', _("Language")) if not req.authname or req.authname == 'anonymous': yield ('advanced', _("Advanced")) def render_preference_panel(self, req, panel): if req.method == 'POST': if 'restore' in req.args: self._do_load(req) else: self._do_save(req) req.redirect(req.href.prefs(panel or None)) data = { 'settings': {'session': req.session, 'session_id': req.session.sid}, 'timezones': all_timezones, 'timezone': get_timezone, 'localtz': localtz, 'has_babel': False } if Locale: locale_ids = get_available_locales() locales = [Locale.parse(locale) for locale in locale_ids] # use locale identifiers from get_available_locales() instead # of str(locale) to prevent storing expanded locale identifier # to session, e.g. zh_Hans_CN and zh_Hant_TW, since Babel 1.0. # see #11258. languages = sorted((id, locale.display_name) for id, locale in zip(locale_ids, locales)) data['locales'] = locales data['languages'] = languages data['has_babel'] = True return 'prefs_%s.html' % (panel or 'general'), data # ITemplateProvider methods def get_htdocs_dirs(self): return [] def get_templates_dirs(self): return [pkg_resources.resource_filename('trac.prefs', 'templates')] # Internal methods def _do_save_xhr(self, req): for key in req.args: if not key in ['save_prefs', 'panel_id', '__FORM_TOKEN']: req.session[key] = req.args[key] req.session.save() req.send_no_content() def _do_save(self, req): language = req.session.get('language') for field in self._form_fields: val = req.args.get(field, '').strip() if val: if field == 'tz' and 'tz' in req.session and \ val not in all_timezones: del req.session['tz'] elif field == 'newsid': req.session.change_sid(val) elif field == 'accesskeys': req.session[field] = '1' else: req.session[field] = val elif field in req.session and (field in req.args or field + '_cb' in req.args): del req.session[field] if Locale and req.session.get('language') != language: # reactivate translations with new language setting when changed del req.locale # for re-negotiating locale deactivate() make_activable(lambda: req.locale, self.env.path) add_notice(req, _("Your preferences have been saved.")) def _do_load(self, req): if req.authname == 'anonymous': oldsid = req.args.get('loadsid') if oldsid: req.session.get_session(oldsid) add_notice(req, _("The session has been loaded.")) trac-1.0.9+dfsg/trac/prefs/templates/0000755000175000017500000000000012574541731015604 5ustar wmbwmbtrac-1.0.9+dfsg/trac/prefs/templates/prefs_general.html0000644000175000017500000000327712574541640021316 0ustar wmbwmb General

This information is used to automatically populate some forms on this site with your contact details. This information is used to associate your login name with your email address and full name, which is used for email notification and RSS feeds, for example.

trac-1.0.9+dfsg/trac/prefs/templates/prefs_userinterface.html0000644000175000017500000000326312574541640022533 0ustar wmbwmb User Interface

Display only the icon or symbol for short inline buttons, and hide the text caption.

Don't show the various help links. This reduces the verbosity of the pages.

trac-1.0.9+dfsg/trac/prefs/templates/prefs_language.html0000644000175000017500000000612512574541640021457 0ustar wmbwmb Language
Install Babel for extended language support. Message catalogs have not been compiled.

Configuring your language will result in all text displayed on this site to use your language instead of that of the server.

The Default language option uses the browser's language negotiation feature to select the appropriate language.

Trac has been localized to more than a dozen of languages but in order to be able to use them, the Babel package needs to be present when installing Trac. See TracInstall for details. Please contact your Trac administrator to enable existing translations.

trac-1.0.9+dfsg/trac/prefs/templates/prefs_advanced.html0000644000175000017500000000453112574541640021440 0ustar wmbwmb Advanced

The session key is used to identify stored custom settings and session data on the server. Although it is automatically generated by default, you may change it to something easier to remember at any time if you wish to load your settings in a different web browser.

You may load a previously created session by entering the corresponding session key below. This lets you share settings between multiple computers and web browsers.

trac-1.0.9+dfsg/trac/prefs/templates/prefs_pygments.html0000644000175000017500000000434112574541640021540 0ustar wmbwmb Syntax Highlighting

The Pygments syntax highlighter can be used with different coloring styles.

Preview:
${output}
trac-1.0.9+dfsg/trac/prefs/templates/prefs_keybindings.html0000644000175000017500000000302312574541640022174 0ustar wmbwmb Keyboard Shortcuts
trac-1.0.9+dfsg/trac/prefs/templates/prefs.html0000644000175000017500000000403312574541640017610 0ustar wmbwmb Preferences: ${select('title/text()')} ${select("*[local-name() != 'title']")}

Preferences

This page lets you customize your personal settings for this site. These settings are stored on the server and are identified by a session key stored in a browser cookie. That cookie allows your settings to be restored on subsequent visits.

${select("*|text()")}
trac-1.0.9+dfsg/trac/prefs/templates/prefs_datetime.html0000644000175000017500000001155412574541640021472 0ustar wmbwmb Date & Time

Configuring your time zone will result in all dates and times displayed on this site to use your time zone instead of that of the server.

Example: The current time is ${format_time(now, 'iso8601', tzinfo=utc)} (UTC).
In your time zone ${nowtz.tzname()}, this would be displayed as $formatted. In the default time zone, this would be displayed as $formatted.

Note: Universal Co-ordinated Time (UTC) is also known as Greenwich Mean Time (GMT).
A positive offset is used to indicate a timezone at the east of Greenwich, i.e. ahead of Universal Time.

Configuring your date format will result in formatting and parsing datetime displayed on this site to use your date format instead of that of the server.

Configuring your relative/absolute format will result in formatting datetime displayed on this site to use your format instead of that of the server.

trac-1.0.9+dfsg/trac/prefs/__init__.py0000644000175000017500000000101612574541640015714 0ustar wmbwmb# -*- coding: utf-8 -*- # # Copyright (C) 2006-2014 Edgewall Software # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.org/wiki/TracLicense. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://trac.edgewall.org/log/. from trac.prefs.api import * trac-1.0.9+dfsg/trac/prefs/tests/0000755000175000017500000000000012574541731014750 5ustar wmbwmbtrac-1.0.9+dfsg/trac/prefs/tests/__init__.py0000644000175000017500000000105112574541640017055 0ustar wmbwmb# -*- coding: utf-8 -*- # # Copyright (C) 2008-2013 Edgewall Software # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.org/wiki/TracLicense. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://trac.edgewall.org/log/. from trac.prefs.tests.functional import functionalSuite trac-1.0.9+dfsg/trac/prefs/tests/functional.py0000755000175000017500000001665412574541640017502 0ustar wmbwmb#!/usr/bin/env python # -*- coding: utf-8 -*- # # Copyright (C) 2008-2013 Edgewall Software # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.org/wiki/TracLicense. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://trac.edgewall.org/log/. import re import unittest from trac.tests.functional import FunctionalTwillTestCaseSetup, \ internal_error, tc #TODO: split this into multiple smaller testcases class TestPreferences(FunctionalTwillTestCaseSetup): def runTest(self): """Set preferences for admin user""" self._tester.go_to_preferences() tc.notfind('Your preferences have been saved.') tc.formvalue('userprefs', 'name', ' System Administrator ') tc.formvalue('userprefs', 'email', ' admin@example.com ') tc.submit() tc.find('Your preferences have been saved.') self._tester.go_to_preferences("Date & Time") tc.formvalue('userprefs', 'tz', 'GMT -10:00') tc.submit() tc.find('Your preferences have been saved.') self._tester.go_to_preferences() tc.notfind('Your preferences have been saved.') tc.find('value="System Administrator"') tc.find(r'value="admin@example\.com"') self._tester.go_to_preferences("Date & Time") tc.find('GMT -10:00') class RegressionTestRev5785(FunctionalTwillTestCaseSetup): def runTest(self): """Test for regression of the fix in r5785""" self._tester.go_to_preferences() tc.submit('logout', 'logout') tc.notfind(internal_error) # See [5785] tc.follow('Login') class RegressionTestTicket5765(FunctionalTwillTestCaseSetup): def runTest(self): """Test for regression of http://trac.edgewall.org/ticket/5765 Unable to turn off 'Enable access keys' in Preferences """ self._tester.go_to_preferences("Keyboard Shortcuts") tc.formvalue('userprefs', 'accesskeys', True) tc.submit() tc.find('name="accesskeys".*checked="checked"') tc.formvalue('userprefs', 'accesskeys', False) tc.submit() tc.notfind('name="accesskeys".*checked="checked"') class RegressionTestTicket11319(FunctionalTwillTestCaseSetup): def runTest(self): """Test for regression of http://trac.edgewall.org/ticket/11319 Only alphanumeric characters can be used for session key in advanced panel. """ try: self._tester.logout() self._tester.go_to_preferences('Advanced') tc.formvalue('userprefs', 'newsid', 'śeśśion_id') tc.submit('change') tc.notfind(internal_error) tc.find('Session ID must be alphanumeric') self._tester.go_to_preferences('Advanced') tc.formvalue('userprefs', 'loadsid', 'śeśśion_id') tc.submit('restore') tc.notfind(internal_error) tc.find('Session ID must be alphanumeric') finally: self._tester.login('admin') class RegressionTestTicket11337(FunctionalTwillTestCaseSetup): def runTest(self): """Test for regression of http://trac.edgewall.org/ticket/11337 The preferences panel will only be visible when Babel is installed or for a user that has `TRAC_ADMIN`. """ from trac.util.translation import has_babel, get_available_locales babel_hint = "Install Babel for extended language support." catalog_hint = "Message catalogs have not been compiled." language_select = '' self._tester.go_to_preferences("Language") if has_babel: tc.notfind(babel_hint) if get_available_locales(): tc.find(language_select) tc.notfind(catalog_hint) else: tc.find(disabled_language_select) tc.find(catalog_hint) else: tc.find(babel_hint) tc.find(disabled_language_select) tc.notfind(catalog_hint) # For users without TRAC_ADMIN, the Language tab should only be # present when Babel is installed self._tester.go_to_preferences() language_tab = '
  • ' try: self._tester.logout() if has_babel: tc.find(language_tab) tc.notfind(catalog_hint) else: tc.notfind(language_tab) finally: self._tester.login('admin') class RegressionTestTicket11515(FunctionalTwillTestCaseSetup): def runTest(self): """Test for regression of http://trac.edgewall.org/ticket/11515 Show a notice message with new language setting after it is changed. """ from trac.util.translation import has_babel, get_available_locales from pkg_resources import resource_exists, resource_filename if not has_babel: return if not resource_exists('trac', 'locale'): return locale_dir = resource_filename('trac', 'locale') from babel.support import Translations string = 'Your preferences have been saved.' translated = None for second_locale in get_available_locales(): tx = Translations.load(locale_dir, second_locale) translated = tx.dgettext('messages', string) if string != translated: break # the locale has a translation else: return try: self._tester.go_to_preferences('Language') tc.formvalue('userprefs', 'language', second_locale) tc.submit() tc.find(re.escape(translated)) finally: tc.formvalue('userprefs', 'language', '') # revert to default tc.submit() tc.find('Your preferences have been saved') class RegressionTestTicket11531(FunctionalTwillTestCaseSetup): """Test for regression of http://trac.edgewall.org/ticket/11531 PreferencesModule can be set as the default_handler.""" def runTest(self): default_handler = self._testenv.get_config('trac', 'default_handler') self._testenv.set_config('trac', 'default_handler', 'PreferencesModule') try: tc.go(self._tester.url) tc.notfind(internal_error) tc.find(r"\bPreferences\b") finally: self._testenv.set_config('trac', 'default_handler', default_handler) def functionalSuite(suite=None): if not suite: import trac.tests.functional suite = trac.tests.functional.functionalSuite() suite.addTest(TestPreferences()) suite.addTest(RegressionTestRev5785()) suite.addTest(RegressionTestTicket5765()) suite.addTest(RegressionTestTicket11319()) suite.addTest(RegressionTestTicket11337()) suite.addTest(RegressionTestTicket11515()) suite.addTest(RegressionTestTicket11531()) return suite suite = functionalSuite if __name__ == '__main__': unittest.main(defaultTest='suite') trac-1.0.9+dfsg/trac/prefs/api.py0000644000175000017500000000206612574541640014734 0ustar wmbwmb# -*- coding: utf-8 -*- # # Copyright (C) 2006-2014 Edgewall Software # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.org/wiki/TracLicense. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://trac.edgewall.org/log/. from trac.core import * class IPreferencePanelProvider(Interface): def get_preference_panels(req): """Return a list of available preference panels. The items returned by this function must be tuple of the form `(panel, label)`. """ def render_preference_panel(req, panel): """Process a request for a preference panel. This function should return a tuple of the form `(template, data)`, where `template` is the name of the template to use and `data` is the data to be passed to the template. """ trac-1.0.9+dfsg/trac/config.py0000644000175000017500000007524412574541636014326 0ustar wmbwmb# -*- coding: utf-8 -*- # # Copyright (C) 2005-2009 Edgewall Software # Copyright (C) 2005-2007 Christopher Lenz # All rights reserved. # # This software is licensed as described in the file COPYING, which # you should have received as part of this distribution. The terms # are also available at http://trac.edgewall.org/wiki/TracLicense. # # This software consists of voluntary contributions made by many # individuals. For the exact contribution history, see the revision # history and logs, available at http://trac.edgewall.org/log/. from __future__ import with_statement from ConfigParser import ConfigParser from copy import deepcopy import os.path from genshi.builder import tag from trac.admin import AdminCommandError, IAdminCommandProvider from trac.core import * from trac.util import AtomicFile, as_bool from trac.util.compat import cleandoc, wait_for_file_mtime_change from trac.util.text import printout, to_unicode, CRLF from trac.util.translation import _, N_, tag_ __all__ = ['Configuration', 'ConfigSection', 'Option', 'BoolOption', 'IntOption', 'FloatOption', 'ListOption', 'ChoiceOption', 'PathOption', 'ExtensionOption', 'OrderedExtensionsOption', 'ConfigurationError'] # Retained for backward-compatibility, use as_bool() instead _TRUE_VALUES = ('yes', 'true', 'enabled', 'on', 'aye', '1', 1, True) _use_default = object() def _to_utf8(basestr): return to_unicode(basestr).encode('utf-8') class ConfigurationError(TracError): """Exception raised when a value in the configuration file is not valid.""" title = N_('Configuration Error') def __init__(self, message=None, title=None, show_traceback=False): if message is None: message = _("Look in the Trac log for more information.") super(ConfigurationError, self).__init__(message, title, show_traceback) class Configuration(object): """Thin layer over `ConfigParser` from the Python standard library. In addition to providing some convenience methods, the class remembers the last modification time of the configuration file, and reparses it when the file has changed. """ def __init__(self, filename, params={}): self.filename = filename self.parser = ConfigParser() self._old_sections = {} self.parents = [] self._lastmtime = 0 self._sections = {} self.parse_if_needed(force=True) def __contains__(self, name): """Return whether the configuration contains a section of the given name. """ return name in self.sections() def __getitem__(self, name): """Return the configuration section with the specified name.""" if name not in self._sections: self._sections[name] = Section(self, name) return self._sections[name] def __repr__(self): return '<%s %r>' % (self.__class__.__name__, self.filename) def get(self, section, key, default=''): """Return the value of the specified option. Valid default input is a string. Returns a string. """ return self[section].get(key, default) def getbool(self, section, key, default=''): """Return the specified option as boolean value. If the value of the option is one of "yes", "true", "enabled", "on", or "1", this method wll return `True`, otherwise `False`. Valid default input is a string or a bool. Returns a bool. (since Trac 0.9.3, "enabled" added in 0.11) """ return self[section].getbool(key, default) def getint(self, section, key, default=''): """Return the value of the specified option as integer. If the specified option can not be converted to an integer, a `ConfigurationError` exception is raised. Valid default input is a string or an int. Returns an int. (since Trac 0.10) """ return self[section].getint(key, default) def getfloat(self, section, key, default=''): """Return the value of the specified option as float. If the specified option can not be converted to a float, a `ConfigurationError` exception is raised. Valid default input is a string, float or int. Returns a float. (since Trac 0.12) """ return self[section].getfloat(key, default) def getlist(self, section, key, default='', sep=',', keep_empty=False): """Return a list of values that have been specified as a single comma-separated option. A different separator can be specified using the `sep` parameter. If the `keep_empty` parameter is set to `True`, empty elements are included in the list. Valid default input is a string or a list. Returns a string. (since Trac 0.10) """ return self[section].getlist(key, default, sep, keep_empty) def getpath(self, section, key, default=''): """Return a configuration value as an absolute path. Relative paths are resolved relative to the location of this configuration file. Valid default input is a string. Returns a normalized path. (enabled since Trac 0.11.5) """ return self[section].getpath(key, default) def set(self, section, key, value): """Change a configuration value. These changes are not persistent unless saved with `save()`. """ self[section].set(key, value) def defaults(self, compmgr=None): """Returns a dictionary of the default configuration values (''since 0.10''). If `compmgr` is specified, return only options declared in components that are enabled in the given `ComponentManager`. """ defaults = {} for (section, key), option in Option.get_registry(compmgr).items(): defaults.setdefault(section, {})[key] = option.default return defaults def options(self, section, compmgr=None): """Return a list of `(name, value)` tuples for every option in the specified section. This includes options that have default values that haven't been overridden. If `compmgr` is specified, only return default option values for components that are enabled in the given `ComponentManager`. """ return self[section].options(compmgr) def remove(self, section, key): """Remove the specified option.""" self[section].remove(key) def sections(self, compmgr=None, defaults=True): """Return a list of section names. If `compmgr` is specified, only the section names corresponding to options declared in components that are enabled in the given `ComponentManager` are returned. """ sections = set([to_unicode(s) for s in self.parser.sections()]) for parent in self.parents: sections.update(parent.sections(compmgr, defaults=False)) if defaults: sections.update(self.defaults(compmgr)) return sorted(sections) def has_option(self, section, option, defaults=True): """Returns True if option exists in section in either the project trac.ini or one of the parents, or is available through the Option registry. (since Trac 0.11) """ section_str = _to_utf8(section) if self.parser.has_section(section_str): if _to_utf8(option) in self.parser.options(section_str): return True for parent in self.parents: if parent.has_option(section, option, defaults=False): return True return defaults and (section, option) in Option.registry def save(self): """Write the configuration options to the primary file.""" if not self.filename: return # Only save options that differ from the defaults sections = [] for section in self.sections(): section_str = _to_utf8(section) options = [] for option in self[section]: default_str = None for parent in self.parents: if parent.has_option(section, option, defaults=False): default_str = _to_utf8(parent.get(section, option)) break option_str = _to_utf8(option) current_str = False if self.parser.has_option(section_str, option_str): current_str = self.parser.get(section_str, option_str) if current_str is not False and current_str != default_str: options.append((option_str, current_str)) if options: sections.append((section_str, sorted(options))) # At this point, all the strings in `sections` are UTF-8 encoded `str` try: wait_for_file_mtime_change(self.filename) with AtomicFile(self.filename, 'w') as fileobj: fileobj.write('# -*- coding: utf-8 -*-\n\n') for section_str, options in sections: fileobj.write('[%s]\n' % section_str) section = to_unicode(section_str) for key_str, val_str in options: if to_unicode(key_str) in self[section].overridden: fileobj.write('# %s = \n' % key_str) else: val_str = val_str.replace(CRLF, '\n') \ .replace('\n', '\n ') fileobj.write('%s = %s\n' % (key_str, val_str)) fileobj.write('\n') self._old_sections = deepcopy(self.parser._sections) except Exception: # Revert all changes to avoid inconsistencies self.parser._sections = deepcopy(self._old_sections) raise def parse_if_needed(self, force=False): if not self.filename or not os.path.isfile(self.filename): return False changed = False modtime = os.path.getmtime(self.filename) if force or modtime != self._lastmtime: self.parser._sections = {} if not self.parser.read(self.filename): raise TracError(_("Error reading '%(file)s', make sure it is " "readable.", file=self.filename)) self._lastmtime = modtime self._old_sections = deepcopy(self.parser._sections) changed = True if changed: self.parents = [] if self.parser.has_option('inherit', 'file'): for filename in self.parser.get('inherit', 'file').split(','): filename = to_unicode(filename.strip()) if not os.path.isabs(filename): filename = os.path.join(os.path.dirname(self.filename), filename) self.parents.append(Configuration(filename)) else: for parent in self.parents: changed |= parent.parse_if_needed(force=force) if changed: self._sections = {} return changed def touch(self): if self.filename and os.path.isfile(self.filename) \ and os.access(self.filename, os.W_OK): wait_for_file_mtime_change(self.filename) def set_defaults(self, compmgr=None): """Retrieve all default values and store them explicitly in the configuration, so that they can be saved to file. Values already set in the configuration are not overridden. """ for (section, name), option in Option.get_registry(compmgr).items(): if not self.parser.has_option(_to_utf8(section), _to_utf8(name)): value = option.default if any(parent[section].contains(name, defaults=False) for parent in self.parents): value = None if value is not None: value = option.dumps(value) self.set(section, name, value) class Section(object): """Proxy for a specific configuration section. Objects of this class should not be instantiated directly. """ __slots__ = ['config', 'name', 'overridden', '_cache'] def __init__(self, config, name): self.config = config self.name = name self.overridden = {} self._cache = {} def contains(self, key, defaults=True): if self.config.parser.has_option(_to_utf8(self.name), _to_utf8(key)): return True for parent in self.config.parents: if parent[self.name].contains(key, defaults=False): return True return defaults and (self.name, key) in Option.registry __contains__ = contains def iterate(self, compmgr=None, defaults=True): """Iterate over the options in this section. If `compmgr` is specified, only return default option values for components that are enabled in the given `ComponentManager`. """ options = set() name_str = _to_utf8(self.name) if self.config.parser.has_section(name_str): for option_str in self.config.parser.options(name_str): option = to_unicode(option_str) options.add(option.lower()) yield option for parent in self.config.parents: for option in parent[self.name].iterate(defaults=False): loption = option.lower() if loption not in options: options.add(loption) yield option if defaults: for section, option in Option.get_registry(compmgr).keys(): if section == self.name and option.lower() not in options: yield option __iter__ = iterate def __repr__(self): return '<%s [%s]>' % (self.__class__.__name__, self.name) def get(self, key, default=''): """Return the value of the specified option. Valid default input is a string. Returns a string. """ cached = self._cache.get(key, _use_default) if cached is not _use_default: return cached name_str = _to_utf8(self.name) key_str = _to_utf8(key) if self.config.parser.has_option(name_str, key_str): value = self.config.parser.get(name_str, key_str) else: for parent in self.config.parents: value = parent[self.name].get(key, _use_default) if value is not _use_default: break else: if default is not _use_default: option = Option.registry.get((self.name, key)) value = option.default if option else _use_default else: value = _use_default if value is _use_default: return default if not value: value = u'' elif isinstance(value, basestring): value = to_unicode(value) self._cache[key] = value return value def getbool(self, key, default=''): """Return the value of the specified option as boolean. This method returns `True` if the option value is one of "yes", "true", "enabled", "on", or non-zero numbers, ignoring case. Otherwise `False` is returned. Valid default input is a string or a bool. Returns a bool. """ return as_bool(self.get(key, default)) def getint(self, key, default=''): """Return the value of the specified option as integer. If the specified option can not be converted to an integer, a `ConfigurationError` exception is raised. Valid default input is a string or an int. Returns an int. """ value = self.get(key, default) if not value: return 0 try: return int(value) except ValueError: raise ConfigurationError( _('[%(section)s] %(entry)s: expected integer, got %(value)s', section=self.name, entry=key, value=repr(value))) def getfloat(self, key, default=''): """Return the value of the specified option as float. If the specified option can not be converted to a float, a `ConfigurationError` exception is raised. Valid default input is a string, float or int. Returns a float. """ value = self.get(key, default) if not value: return 0.0 try: return float(value) except ValueError: raise ConfigurationError( _('[%(section)s] %(entry)s: expected float, got %(value)s', section=self.name, entry=key, value=repr(value))) def getlist(self, key, default='', sep=',', keep_empty=True): """Return a list of values that have been specified as a single comma-separated option. A different separator can be specified using the `sep` parameter. If the `keep_empty` parameter is set to `False`, empty elements are omitted from the list. Valid default input is a string or a list. Returns a list. """ value = self.get(key, default) if not value: return [] if isinstance(value, basestring): items = [item.strip() for item in value.split(sep)] else: items = list(value) if not keep_empty: items = [item for item in items if item not in (None, '')] return items def getpath(self, key, default=''): """Return the value of the specified option as a path, relative to the location of this configuration file. Valid default input is a string. Returns a normalized path. """ path = self.get(key, default) if not path: return default if not os.path.isabs(path): path = os.path.join(os.path.dirname(self.config.filename), path) return os.path.normcase(os.path.realpath(path)) def options(self, compmgr=None): """Return `(key, value)` tuples for every option in the section. This includes options that have default values that haven't been overridden. If `compmgr` is specified, only return default option values for components that are enabled in the given `ComponentManager`. """ for key in self.iterate(compmgr): yield key, self.get(key) def set(self, key, value): """Change a configuration value. These changes are not persistent unless saved with `save()`. """ self._cache.pop(key, None) name_str = _to_utf8(self.name) key_str = _to_utf8(key) if not self.config.parser.has_section(name_str): self.config.parser.add_section(name_str) if value is None: self.overridden[key] = True value_str = '' else: value_str = _to_utf8(value) return self.config.parser.set(name_str, key_str, value_str) def remove(self, key): """Delete a key from this section. Like for `set()`, the changes won't persist until `save()` gets called. """ name_str = _to_utf8(self.name) if self.config.parser.has_section(name_str): self._cache.pop(key, None) self.config.parser.remove_option(_to_utf8(self.name), _to_utf8(key)) def _get_registry(cls, compmgr=None): """Return the descriptor registry. If `compmgr` is specified, only return descriptors for components that are enabled in the given `ComponentManager`. """ if compmgr is None: return cls.registry from trac.core import ComponentMeta components = {} for comp in ComponentMeta._components: for attr in comp.__dict__.itervalues(): if isinstance(attr, cls): components[attr] = comp return dict(each for each in cls.registry.iteritems() if each[1] not in components or compmgr.is_enabled(components[each[1]])) class ConfigSection(object): """Descriptor for configuration sections.""" registry = {} @staticmethod def get_registry(compmgr=None): """Return the section registry, as a `dict` mapping section names to `ConfigSection` objects. If `compmgr` is specified, only return sections for components that are enabled in the given `ComponentManager`. """ return _get_registry(ConfigSection, compmgr) def __init__(self, name, doc, doc_domain='tracini'): """Create the configuration section.""" self.name = name self.registry[self.name] = self self.__doc__ = cleandoc(doc) self.doc_domain = doc_domain def __get__(self, instance, owner): if instance is None: return self config = getattr(instance, 'config', None) if config and isinstance(config, Configuration): return config[self.name] def __repr__(self): return '<%s [%s]>' % (self.__class__.__name__, self.name) class Option(object): """Descriptor for configuration options.""" registry = {} def accessor(self, section, name, default): return section.get(name, default) @staticmethod def get_registry(compmgr=None): """Return the option registry, as a `dict` mapping `(section, key)` tuples to `Option` objects. If `compmgr` is specified, only return options for components that are enabled in the given `ComponentManager`. """ return _get_registry(Option, compmgr) def __init__(self, section, name, default=None, doc='', doc_domain='tracini'): """Create the configuration option. @param section: the name of the configuration section this option belongs to @param name: the name of the option @param default: the default value for the option @param doc: documentation of the option """ self.section = section self.name = name self.default = default self.registry[(self.section, self.name)] = self self.__doc__ = cleandoc(doc) self.doc_domain = doc_domain def __get__(self, instance, owner): if instance is None: return self config = getattr(instance, 'config', None) if config and isinstance(config, Configuration): section = config[self.section] value = self.accessor(section, self.name, self.default) return value def __set__(self, instance, value): raise AttributeError(_("Setting attribute is not allowed.")) def __repr__(self): return '<%s [%s] "%s">' % (self.__class__.__name__, self.section, self.name) def dumps(self, value): """Return the value as a string to write to a trac.ini file""" if value is None: return '' if value is True: return 'enabled' if value is False: return 'disabled' if isinstance(value, unicode): return value return to_unicode(value) class BoolOption(Option): """Descriptor for boolean configuration options.""" def accessor(self, section, name, default): return section.getbool(name, default) class IntOption(Option): """Descriptor for integer configuration options.""" def accessor(self, section, name, default): return section.getint(name, default) class FloatOption(Option): """Descriptor for float configuration options.""" def accessor(self, section, name, default): return section.getfloat(name, default) class ListOption(Option): """Descriptor for configuration options that contain multiple values separated by a specific character. """ def __init__(self, section, name, default=None, sep=',', keep_empty=False, doc='', doc_domain='tracini'): Option.__init__(self, section, name, default, doc, doc_domain) self.sep = sep self.keep_empty = keep_empty def accessor(self, section, name, default): return section.getlist(name, default, self.sep, self.keep_empty) def dumps(self, value): if isinstance(value, (list, tuple)): return self.sep.join(Option.dumps(self, v) or '' for v in value) return Option.dumps(self, value) class ChoiceOption(Option): """Descriptor for configuration options providing a choice among a list of items. The default value is the first choice in the list. """ def __init__(self, section, name, choices, doc='', doc_domain='tracini'): Option.__init__(self, section, name, _to_utf8(choices[0]), doc, doc_domain) self.choices = set(_to_utf8(choice).strip() for choice in choices) def accessor(self, section, name, default): value = section.get(name, default) if value not in self.choices: raise ConfigurationError( _('[%(section)s] %(entry)s: expected one of ' '(%(choices)s), got %(value)s', section=section.name, entry=name, value=repr(value), choices=', '.join('"%s"' % c for c in sorted(self.choices)))) return value class PathOption(Option): """Descriptor for file system path configuration options. Relative paths are resolved to absolute paths using the directory containing the configuration file as the reference. """ def accessor(self, section, name, default): return section.getpath(name, default) class ExtensionOption(Option): """Name of a component implementing `interface`. Raises a `ConfigurationError` if the component cannot be found in the list of active components implementing the interface.""" def __init__(self, section, name, interface, default=None, doc='', doc_domain='tracini'): Option.__init__(self, section, name, default, doc, doc_domain) self.xtnpt = ExtensionPoint(interface) def __get__(self, instance, owner): if instance is None: return self value = Option.__get__(self, instance, owner) for impl in self.xtnpt.extensions(instance): if impl.__class__.__name__ == value: return impl raise ConfigurationError( tag_("Cannot find an implementation of the %(interface)s " "interface named %(implementation)s. Please check " "that the Component is enabled or update the option " "%(option)s in trac.ini.", interface=tag.tt(self.xtnpt.interface.__name__), implementation=tag.tt(value), option=tag.tt("[%s] %s" % (self.section, self.name)))) class OrderedExtensionsOption(ListOption): """A comma separated, ordered, list of components implementing `interface`. Can be empty. If `include_missing` is true (the default) all components implementing the interface are returned, with those specified by the option ordered first.""" def __init__(self, section, name, interface, default=None, include_missing=True, doc='', doc_domain='tracini'): ListOption.__init__(self, section, name, default, doc=doc, doc_domain=doc_domain) self.xtnpt = ExtensionPoint(interface) self.include_missing = include_missing def __get__(self, instance, owner): if instance is None: return self order = ListOption.__get__(self, instance, owner) components = [] implementing_classes = [] for impl in self.xtnpt.extensions(instance): implementing_classes.append(impl.__class__.__name__) if self.include_missing or impl.__class__.__name__ in order: components.append(impl) not_found = sorted(set(order) - set(implementing_classes)) if not_found: raise ConfigurationError( tag_("Cannot find implementation(s) of the %(interface)s " "interface named %(implementation)s. Please check " "that the Component is enabled or update the option " "%(option)s in trac.ini.", interface=tag.tt(self.xtnpt.interface.__name__), implementation=tag( (', ' if idx != 0 else None, tag.tt(impl)) for idx, impl in enumerate(not_found)), option=tag.tt("[%s] %s" % (self.section, self.name)))) def compare(x, y): x, y = x.__class__.__name__, y.__class__.__name__ if x not in order: return int(y in order) if y not in order: return -int(x in order) return cmp(order.index(x), order.index(y)) components.sort(compare) return components class ConfigurationAdmin(Component): """trac-admin command provider for trac.ini administration.""" implements(IAdminCommandProvider) # IAdminCommandProvider methods def get_admin_commands(self): yield ('config get', '