Hi,

Just wondering how people felt about Olivier's contribution so far and the idea in general. Obviously the code does need a bit more work as we probably need to have product repositories isolated from the global environment.

While I think we definitely should maintain global repositories that products can link to, the ability to keep a repository private to a specific product, even in the admin, makes a lot of sense to me.

Cheers,
    Gary

On 07/11/13 16:40, Olivier Mauras wrote:

Please find attached two patches.
First one grants product owner admin rights to his products, and change the repository management part to the one used by global one. This makes the product owner to create/delete/alias repositories. For the moment it's global, it would require some changes at DB level to make the repositories isolated per product. I think a new table - "product_repositories" - with a new "DbRepositoryProvider" class would be the less intrusive...

The second small patch modifies the theme to get "Source" tab to point to product/<id>/browser instead of getting the wiki. This indeed gives a "No node /" error as i haven't yet found my way in the code that would point the product browser to the repository.

Sorry for the nasty two patches, i worked on installed app instead of the code... This is all based on the latest stable 0.7.0.

Hope this will help.

Olivier


bh_p1.patch


diff --git a/product_admin.py b/product_admin.py
index 9c0fc42..df69e04 100644
--- a/product_admin.py
+++ b/product_admin.py
@@ -18,6 +18,8 @@
"""Admin panels for product management""" +from genshi.builder import tag
+
  from trac.admin.api import IAdminCommandProvider, AdminCommandError,\
      AdminCommandManager
  from trac.admin.console import TracAdmin, TRAC_VERSION
@@ -27,9 +29,9 @@ from trac.config import *
  from trac.perm import PermissionSystem
  from trac.resource import ResourceNotFound
  from trac.ticket.admin import TicketAdminPanel, _save_config
-from trac.util import lazy
-from trac.util.text import print_table, to_unicode, printerr, printout
-from trac.util.translation import _, N_, gettext, ngettext
+from trac.util import lazy, as_bool
+from trac.util.text import print_table, to_unicode, printerr, printout, 
breakable_path, normalize_whitespace
+from trac.util.translation import _, N_, gettext, ngettext, tag_
  from trac.web.api import HTTPNotFound, IRequestFilter, IRequestHandler
  from trac.web.chrome import Chrome, add_notice, add_warning
@@ -37,9 +39,10 @@ from multiproduct.env import ProductEnvironment
  from multiproduct.model import Product
  from multiproduct.perm import sudo
-import multiproduct.versioncontrol
+#import multiproduct.versioncontrol
  import trac.versioncontrol.admin
-from trac.versioncontrol import DbRepositoryProvider, RepositoryManager
+from trac.versioncontrol import DbRepositoryProvider, RepositoryManager, \
+                                is_default
  from multiproduct.util import ReplacementComponent
#--------------------------
@@ -335,8 +338,7 @@ class ProductAdminModule(Component):
          """
          if isinstance(self.env, ProductEnvironment) and \
                  handler is AdminModule(self.env) and \
-                not req.perm.has_permission('TRAC_ADMIN') and \
-                req.perm.has_permission('PRODUCT_ADMIN'):
+                not req.perm.has_permission('PRODUCT_ADMIN'):
              # Intercept admin request
              return self
          return handler
@@ -410,9 +412,27 @@ class ProductRepositoryAdminPanel(ReplacementComponent, 
trac.versioncontrol.admi
def get_admin_panels(self, req):
          if 'VERSIONCONTROL_ADMIN' in req.perm:
-            yield ('versioncontrol', _('Version Control'), 'repository',
-                   _('Repository Links') if isinstance(self.env, 
ProductEnvironment)
-                    else _('Repositories'))
+            yield ('versioncontrol', _('Version Control'), 'repository',
+                _('Repositories'))
+
+    def _extend_info(self, reponame, info, editable):
+        """Extend repository info for rendering."""
+        info['name'] = reponame
+        if info.get('dir') is not None:
+            info['prettydir'] = breakable_path(info['dir']) or ''
+        info['hidden'] = as_bool(info.get('hidden'))
+        #info['editable'] = editable
+        info['editable'] = True
+        if not info.get('alias'):
+            try:
+                self.log.debug(reponame)
+                repos = 
RepositoryManager(self.env.parent).get_repository(reponame)
+                youngest_rev = repos.get_youngest_rev()
+                info['rev'] = youngest_rev
+                info['display_rev'] = repos.display_rev(youngest_rev)
+            except Exception:
+                pass
+        return info
def render_admin_panel(self, req, category, page, path_info):
          if not isinstance(self.env, ProductEnvironment):
@@ -420,41 +440,143 @@ class ProductRepositoryAdminPanel(ReplacementComponent, 
trac.versioncontrol.admi
                  req, category, page, path_info)
req.perm.require('VERSIONCONTROL_ADMIN')
-        db_provider = self.env[DbRepositoryProvider]
-
-        if req.method == 'POST' and db_provider:
-            if req.args.get('remove'):
-                repolist = req.args.get('sel')
-                if repolist:
-                    if isinstance(repolist, basestring):
-                        repolist = [repolist, ]
-                    for reponame in repolist:
-                        db_provider.unlink_product(reponame)
-            elif req.args.get('addlink') is not None and db_provider:
-                reponame = req.args.get('repository')
-                db_provider.link_product(reponame)
-            req.redirect(req.href.admin(category, page))
-
-        # Retrieve info for all product repositories
-        rm_product = RepositoryManager(self.env)
-        rm_product.reload_repositories()
-        all_product_repos = rm_product.get_all_repositories()
-        repositories = dict((reponame, self._extend_info(
-                                reponame, info.copy(), True))
-                            for (reponame, info) in
-                                all_product_repos.iteritems())
-        types = sorted([''] + rm_product.get_supported_types())
-
-        # construct a list of all repositores not linked to this product
          rm = RepositoryManager(self.env.parent)
          all_repos = rm.get_all_repositories()
-        unlinked_repositories = dict([(k, all_repos[k]) for k in
-            sorted(set(all_repos) - set(all_product_repos))])
+        db_provider = self.env[DbRepositoryProvider]
+
+        if path_info:
+            # Detail view
+            self.log.debug(path_info)
+            reponame = path_info if not is_default(path_info) else ''
+            info = all_repos.get(reponame)
+            if info is None:
+                raise TracError(_("Repository '%(repo)s' not found",
+                                  repo=path_info))
+            if req.method == 'POST':
+                if req.args.get('cancel'):
+                    req.redirect(req.href.admin(category, page))
+
+                elif db_provider and req.args.get('save'):
+                    # Modify repository
+                    changes = {}
+                    for field in db_provider.repository_attrs:
+                        value = normalize_whitespace(req.args.get(field))
+                        if (value is not None or field == 'hidden') \
+                                and value != info.get(field):
+                            changes[field] = value
+                    if 'dir' in changes \
+                            and not self._check_dir(req, changes['dir']):
+                        changes = {}
+                    if changes:
+                        db_provider.modify_repository(reponame, changes)
+                        add_notice(req, _('Your changes have been saved.'))
+                    name = req.args.get('name')
+                    resync = tag.tt('trac-admin $ENV repository resync "%s"'
+                                    % (name or '(default)'))
+                    if 'dir' in changes:
+                        msg = tag_('You should now run %(resync)s to '
+                                   'synchronize Trac with the repository.',
+                                   resync=resync)
+                        add_notice(req, msg)
+                    elif 'type' in changes:
+                        msg = tag_('You may have to run %(resync)s to '
+                                   'synchronize Trac with the repository.',
+                                   resync=resync)
+                        add_notice(req, msg)
+                    if name and name != path_info and not 'alias' in info:
+                        cset_added = tag.tt('trac-admin $ENV changeset '
+                                            'added "%s" $REV'
+                                            % (name or '(default)'))
+                        msg = tag_('You will need to update your post-commit '
+                                   'hook to call %(cset_added)s with the new '
+                                   'repository name.', cset_added=cset_added)
+                        add_notice(req, msg)
+                    if changes:
+                        req.redirect(req.href.admin(category, page))
+
+            Chrome(self.env).add_wiki_toolbars(req)
+            data = {'view': 'detail', 'reponame': reponame}
- data = {'types': types, 'default_type': rm_product.repository_type,
-                'repositories': repositories,
-                'unlinked_repositories': unlinked_repositories}
-        return 'repository_links.html', data
+        else:
+            if req.method == 'POST':
+                # Add a repository
+                if db_provider and req.args.get('add_repos'):
+                    name = req.args.get('name')
+                    type_ = req.args.get('type')
+                    # Avoid errors when copy/pasting paths
+                    dir = normalize_whitespace(req.args.get('dir', ''))
+                    if name is None or type_ is None or not dir:
+                        add_warning(req, _('Missing arguments to add a '
+                                            'repository.'))
+                    elif self._check_dir(req, dir):
+                        db_provider.add_repository(name, dir, type_)
+                        name = name or '(default)'
+                        add_notice(req, _('The repository "%(name)s" has been '
+                                          'added.', name=name))
+                        resync = tag.tt('trac-admin $ENV repository resync '
+                                        '"%s"' % name)
+                        msg = tag_('You should now run %(resync)s to '
+                                   'synchronize Trac with the repository.',
+                                    resync=resync)
+                        add_notice(req, msg)
+                        cset_added = tag.tt('trac-admin $ENV changeset '
+                                            'added "%s" $REV' % name)
+                        msg = tag_('You should also set up a post-commit hook '
+                                   'on the repository to call %(cset_added)s '
+                                   'for each committed changeset.',
+                                   cset_added=cset_added)
+                        add_notice(req, msg)
+                        req.redirect(req.href.admin(category, page))
+
+                # Add a repository alias
+                elif db_provider and req.args.get('add_alias'):
+                    name = req.args.get('name')
+                    alias = req.args.get('alias')
+                    if name is not None and alias is not None:
+                        db_provider.add_alias(name, alias)
+                        add_notice(req, _('The alias "%(name)s" has been '
+                                          'added.', name=name or '(default)'))
+                        req.redirect(req.href.admin(category, page))
+                    add_warning(req, _('Missing arguments to add an '
+                                       'alias.'))
+
+                # Refresh the list of repositories
+                elif req.args.get('refresh'):
+                    req.redirect(req.href.admin(category, page))
+
+                # Remove repositories
+                elif db_provider and req.args.get('remove'):
+                    sel = req.args.getlist('sel')
+                    if sel:
+                        for name in sel:
+                            db_provider.remove_repository(name)
+                        add_notice(req, _('The selected repositories have '
+                                          'been removed.'))
+                        req.redirect(req.href.admin(category, page))
+                    add_warning(req, _('No repositories were selected.'))
+
+            data = {'view': 'list'}
+
+        # Force repo refresh - should already happen :/
+        rm.reload_repositories()
+
+        #self.log.debug(all_repos)
+
+        db_repos = {}
+        if db_provider is not None:
+            db_repos = dict(db_provider.get_repositories())
+
+        # Prepare common rendering data
+        repositories = dict((reponame, self._extend_info(reponame, info.copy(),
+                                                         reponame in db_repos))
+                            for (reponame, info) in all_repos.iteritems())
+
+        #self.log.debug(repositories)
+        types = sorted([''] + rm.get_supported_types())
+        data.update({'types': types, 'default_type': rm.repository_type,
+                     'repositories': repositories})
+
+        return 'admin_repositories.html', data
trac.versioncontrol.admin.RepositoryAdminPanel = ProductRepositoryAdminPanel

bh_p2.patch


diff --git a/theme.py b/theme.py
index 6c7f81b..8686a26 100644
--- a/theme.py
+++ b/theme.py
@@ -443,8 +443,7 @@ class BloodhoundTheme(ThemeBase):
              bm = self.env[BrowserModule]
              if bm and not list(bm.get_navigation_items(req)):
                  yield ('mainnav', 'browser',
-                       tag.a(_('Browse Source'),
-                             href=req.href.wiki('TracRepositoryAdmin')))
+                       tag.a(_('Browse Source'), href=req.href.browser()))
class QuickCreateTicketDialog(Component):


Reply via email to