Changeset - a437a986d399
[Not reviewed]
Merge default
1 65 2
Marcin Kuzminski - 14 years ago 2012-05-10 20:27:45
marcin@python-works.com
merged beta into stable
67 files changed with 1226 insertions and 569 deletions:
0 comments (0 inline, 0 general)
docs/changelog.rst
Show inline comments
 
modified file chmod 100644 => 100755
 
.. _changelog:
 

	
 
=========
 
Changelog
 
=========
 

	
 
1.3.5 (**2012-05-10**)
 
----------------------
 

	
 
news
 
++++
 

	
 
- use ext_json for json module
 
- unified annotation view with file source view
 
- notification improvements, better inbox + css
 
- #419 don't strip passwords for login forms, make rhodecode 
 
  more compatible with LDAP servers
 
- Added HTTP_X_FORWARDED_FOR as another method of extracting 
 
  IP for pull/push logs. - moved all to base controller  
 
- #415: Adding comment to changeset causes reload. 
 
  Comments are now added via ajax and doesn't reload the page
 
- #374 LDAP config is discarded when LDAP can't be activated
 
- limited push/pull operations are now logged for git in the journal
 
- bumped mercurial to 2.2.X series
 
- added support for displaying submodules in file-browser
 
- #421 added bookmarks in changelog view
 

	
 
fixes
 
+++++
 

	
 
- fixed dev-version marker for stable when served from source codes
 
- fixed missing permission checks on show forks page
 
- #418 cast to unicode fixes in notification objects
 
- #426 fixed mention extracting regex
 
- fixed remote-pulling for git remotes remopositories
 
- fixed #434: Error when accessing files or changesets of a git repository 
 
  with submodules
 
- fixed issue with empty APIKEYS for users after registration ref. #438
 
- fixed issue with getting README files from git repositories
 

	
 
1.3.4 (**2012-03-28**)
 
----------------------
 

	
 
news
 
++++
 

	
 
- Whoosh logging is now controlled by the .ini files logging setup
 
- added clone-url into edit form on /settings page
 
- added help text into repo add/edit forms
 
- created rcextensions module with additional mappings (ref #322) and
 
  post push/pull/create repo hooks callbacks
 
- implemented #377 Users view for his own permissions on account page
 
- #399 added inheritance of permissions for users group on repos groups
 
- #401 repository group is automatically pre-selected when adding repos 
 
  inside a repository group
 
- added alternative HTTP 403 response when client failed to authenticate. Helps 
 
  solving issues with Mercurial and LDAP
 
- #402 removed group prefix from repository name when listing repositories 
 
  inside a group
 
- added gravatars into permission view and permissions autocomplete
 
- #347 when running multiple RhodeCode instances, properly invalidates cache 
 
  for all registered servers
 

	
 
fixes
 
+++++
 

	
 
- fixed #390 cache invalidation problems on repos inside group
 
- fixed #385 clone by ID url was loosing proxy prefix in URL
 
- fixed some unicode problems with waitress
 
- fixed issue with escaping < and > in changeset commits
 
- fixed error occurring during recursive group creation in API 
 
  create_repo function
 
- fixed #393 py2.5 fixes for routes url generator
 
- fixed #397 Private repository groups shows up before login
 
- fixed #396 fixed problems with revoking users in nested groups
 
- fixed mysql unicode issues + specified InnoDB as default engine with 
 
  utf8 charset
 
- #406 trim long branch/tag names in changelog to not break UI
 
  
 
1.3.3 (**2012-03-02**)
 
----------------------
 

	
 
news
 
++++
 

	
 

	
 
fixes
 
+++++
 

	
 
- fixed some python2.5 compatibility issues 
 
- fixed issues with removed repos was accidentally added as groups, after
 
  full rescan of paths
 
- fixes #376 Cannot edit user (using container auth)
 
- fixes #378 Invalid image urls on changeset screen with proxy-prefix 
 
  configuration
 
- fixed initial sorting of repos inside repo group
 
- fixes issue when user tried to resubmit same permission into user/user_groups
 
- bumped beaker version that fixes #375 leap error bug
 
- fixed raw_changeset for git. It was generated with hg patch headers
 
- fixed vcs issue with last_changeset for filenodes
 
- fixed missing commit after hook delete
 
- fixed #372 issues with git operation detection that caused a security issue 
 
  for git repos
 

	
 
1.3.2 (**2012-02-28**)
 
----------------------
 

	
 
news
 
++++
 

	
 

	
 
fixes
 
+++++
 

	
 
- fixed git protocol issues with repos-groups
 
- fixed git remote repos validator that prevented from cloning remote git repos
 
- fixes #370 ending slashes fixes for repo and groups
 
- fixes #368 improved git-protocol detection to handle other clients
 
- fixes #366 When Setting Repository Group To Blank Repo Group Wont Be 
 
  Moved To Root
 
- fixes #371 fixed issues with beaker/sqlalchemy and non-ascii cache keys 
 
- fixed #373 missing cascade drop on user_group_to_perm table
 

	
 
1.3.1 (**2012-02-27**)
 
----------------------
 

	
 
news
 
++++
 

	
 

	
 
fixes
 
+++++
 

	
 
- redirection loop occurs when remember-me wasn't checked during login
 
- fixes issues with git blob history generation 
docs/conf.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
#
 
# RhodeCode documentation build configuration file, created by
 
# sphinx-quickstart on Sun Oct 10 16:46:37 2010.
 
#
 
# This file is execfile()d with the current directory set to its containing dir.
 
#
 
# Note that not all possible configuration values are present in this
 
# autogenerated file.
 
#
 
# All configuration values have a default; values that are commented out
 
# serve to show the default.
 

	
 
import sys, os
 
import sys
 
import os
 
import datetime
 

	
 
# If extensions (or modules to document with autodoc) are in another directory,
 
# add these directories to sys.path here. If the directory is relative to the
 
# documentation root, use os.path.abspath to make it absolute, like shown here.
 
sys.path.insert(0, os.path.abspath('..'))
 

	
 
# -- General configuration -----------------------------------------------------
 

	
 
# If your documentation needs a minimal Sphinx version, state it here.
 
#needs_sphinx = '1.0'
 

	
 
# Add any Sphinx extension module names here, as strings. They can be extensions
 
# coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
 
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.intersphinx', 'sphinx.ext.todo', 'sphinx.ext.viewcode']
 
extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest',
 
              'sphinx.ext.intersphinx', 'sphinx.ext.todo',
 
              'sphinx.ext.viewcode']
 

	
 
# Add any paths that contain templates here, relative to this directory.
 
templates_path = ['_templates']
 

	
 
# The suffix of source filenames.
 
source_suffix = '.rst'
 

	
 
# The encoding of source files.
 
#source_encoding = 'utf-8-sig'
 

	
 
# The master toctree document.
 
master_doc = 'index'
 

	
 
# General information about the project.
 
project = u'RhodeCode'
 
copyright = u'2010, Marcin Kuzminski'
 
copyright = u'%s, Marcin Kuzminski' % (datetime.datetime.now().year)
 

	
 
# The version info for the project you're documenting, acts as replacement for
 
# |version| and |release|, also used in various other places throughout the
 
# built documents.
 
#
 
# The short X.Y version.
 
root = os.path.dirname(os.path.dirname(__file__))
 
sys.path.append(root)
 
from rhodecode import get_version, __version__
 
version = get_version()
 
# The full version, including alpha/beta/rc tags.
 
release = __version__
 

	
 
# The language for content autogenerated by Sphinx. Refer to documentation
 
# for a list of supported languages.
 
#language = None
 

	
 
# There are two options for replacing |today|: either, you set today to some
 
# non-false value, then it is used:
 
#today = ''
 
# Else, today_fmt is used as the format for a strftime call.
 
#today_fmt = '%B %d, %Y'
 

	
 
# List of patterns, relative to source directory, that match files and
 
# directories to ignore when looking for source files.
 
exclude_patterns = ['_build']
 

	
 
# The reST default role (used for this markup: `text`) to use for all documents.
 
#default_role = None
 

	
 
# If true, '()' will be appended to :func: etc. cross-reference text.
 
#add_function_parentheses = True
 

	
 
# If true, the current module name will be prepended to all description
 
# unit titles (such as .. function::).
 
#add_module_names = True
 

	
 
# If true, sectionauthor and moduleauthor directives will be shown in the
 
# output. They are ignored by default.
 
#show_authors = False
 

	
 
# The name of the Pygments (syntax highlighting) style to use.
 
pygments_style = 'sphinx'
 

	
 
# A list of ignored prefixes for module index sorting.
 
#modindex_common_prefix = []
 

	
 

	
 
# -- Options for HTML output ---------------------------------------------------
 

	
 
# The theme to use for HTML and HTML Help pages.  See the documentation for
 
# a list of builtin themes.
 
html_theme = 'nature'
 

	
 
# Theme options are theme-specific and customize the look and feel of a theme
 
# further.  For a list of options available for each theme, see the
 
# documentation.
 
#html_theme_options = {}
 

	
 
# Add any paths that contain custom themes here, relative to this directory.
 
html_theme_path = ['theme']
 

	
 
# The name for this set of Sphinx documents.  If None, it defaults to
 
# "<project> v<release> documentation".
 
#html_title = None
 

	
 
# A shorter title for the navigation bar.  Default is the same as html_title.
 
#html_short_title = None
 

	
 
# The name of an image file (relative to this directory) to place at the top
 
# of the sidebar.
 
#html_logo = None
 

	
 
# The name of an image file (within the static path) to use as favicon of the
 
# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
 
# pixels large.
 
#html_favicon = None
 

	
 
# Add any paths that contain custom static files (such as style sheets) here,
 
# relative to this directory. They are copied after the builtin static files,
 
# so a file named "default.css" will overwrite the builtin "default.css".
 
#html_static_path = ['_static']
 

	
 
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
 
# using the given strftime format.
 
#html_last_updated_fmt = '%b %d, %Y'
 

	
 
# If true, SmartyPants will be used to convert quotes and dashes to
 
# typographically correct entities.
 
#html_use_smartypants = True
 

	
 
# Custom sidebar templates, maps document names to template names.
 
#html_sidebars = {}
 

	
 
# Additional templates that should be rendered to pages, maps page names to
 
# template names.
rhodecode/__init__.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
"""
 
    rhodecode.__init__
 
    ~~~~~~~~~~~~~~~~~~
 

	
 
    RhodeCode, a web based repository management based on pylons
 
    versioning implementation: http://www.python.org/dev/peps/pep-0386/
 

	
 
    :created_on: Apr 9, 2010
 
    :author: marcink
 
    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
 
    :license: GPLv3, see COPYING for more details.
 
"""
 
# This program is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# This program is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
import sys
 
import platform
 

	
 
VERSION = (1, 3, 4)
 
VERSION = (1, 3, 5)
 

	
 
try:
 
    from rhodecode.lib import get_current_revision
 
    _rev = get_current_revision()
 
    if _rev and len(VERSION) > 3:
 
        VERSION += ('dev%s' % _rev[0],)
 
except ImportError:
 
    pass
 

	
 
__version__ = ('.'.join((str(each) for each in VERSION[:3])) +
 
               '.'.join(VERSION[3:]))
 
__dbversion__ = 5  # defines current db version for migrations
 
__platform__ = platform.system()
 
__license__ = 'GPLv3'
 
__py_version__ = sys.version_info
 

	
 
PLATFORM_WIN = ('Windows')
 
PLATFORM_OTHERS = ('Linux', 'Darwin', 'FreeBSD', 'OpenBSD', 'SunOS')
 

	
 
is_windows = __platform__ in PLATFORM_WIN
 
is_unix = __platform__ in PLATFORM_OTHERS
 

	
 
requirements = [
 
    "Pylons==1.0.0",
 
    "Beaker==1.6.3",
 
    "WebHelpers==1.3",
 
    "formencode==1.2.4",
 
    "SQLAlchemy==0.7.6",
 
    "Mako==0.6.2",
 
    "Mako==0.7.0",
 
    "pygments>=1.4",
 
    "whoosh>=2.3.0,<2.4",
 
    "whoosh>=2.4.0,<2.5",
 
    "celery>=2.2.5,<2.3",
 
    "babel",
 
    "python-dateutil>=1.5.0,<2.0.0",
 
    "dulwich>=0.8.4,<0.9.0",
 
    "dulwich>=0.8.5,<0.9.0",
 
    "webob==1.0.8",
 
    "markdown==2.1.1",
 
    "docutils==0.8.1",
 
]
 

	
 
if __py_version__ < (2, 6):
 
    requirements.append("simplejson")
 
    requirements.append("pysqlite")
 

	
 
if __platform__ in PLATFORM_WIN:
 
    requirements.append("mercurial>=2.1,<2.2")
 
if is_windows:
 
    requirements.append("mercurial>=2.2.1,<2.3")
 
else:
 
    requirements.append("py-bcrypt")
 
    requirements.append("mercurial>=2.1,<2.2")
 
    requirements.append("mercurial>=2.2.1,<2.3")
 

	
 

	
 
def get_version():
 
    """Returns shorter version (digit parts only) as string."""
 

	
 
    return '.'.join((str(each) for each in VERSION[:3]))
 

	
 
BACKENDS = {
 
    'hg': 'Mercurial repository',
 
    'git': 'Git repository',
 
}
 

	
 
CELERY_ON = False
 

	
 
# link to config for pylons
 
CONFIG = {}
 

	
 
# Linked module for extensions
 
EXTENSIONS = {}
rhodecode/config/routing.py
Show inline comments
 
@@ -361,147 +361,147 @@ def make_map(config):
 
    #LOGIN/LOGOUT/REGISTER/SIGN IN
 
    rmap.connect('login_home', '%s/login' % ADMIN_PREFIX, controller='login')
 
    rmap.connect('logout_home', '%s/logout' % ADMIN_PREFIX, controller='login',
 
                 action='logout')
 

	
 
    rmap.connect('register', '%s/register' % ADMIN_PREFIX, controller='login',
 
                 action='register')
 

	
 
    rmap.connect('reset_password', '%s/password_reset' % ADMIN_PREFIX,
 
                 controller='login', action='password_reset')
 

	
 
    rmap.connect('reset_password_confirmation',
 
                 '%s/password_reset_confirmation' % ADMIN_PREFIX,
 
                 controller='login', action='password_reset_confirmation')
 

	
 
    #FEEDS
 
    rmap.connect('rss_feed_home', '/{repo_name:.*}/feed/rss',
 
                controller='feed', action='rss',
 
                conditions=dict(function=check_repo))
 

	
 
    rmap.connect('atom_feed_home', '/{repo_name:.*}/feed/atom',
 
                controller='feed', action='atom',
 
                conditions=dict(function=check_repo))
 

	
 
    #==========================================================================
 
    # REPOSITORY ROUTES
 
    #==========================================================================
 
    rmap.connect('summary_home', '/{repo_name:.*}',
 
                controller='summary',
 
                conditions=dict(function=check_repo))
 

	
 
    rmap.connect('repos_group_home', '/{group_name:.*}',
 
                controller='admin/repos_groups', action="show_by_name",
 
                conditions=dict(function=check_group))
 

	
 
    rmap.connect('changeset_home', '/{repo_name:.*}/changeset/{revision}',
 
                controller='changeset', revision='tip',
 
                conditions=dict(function=check_repo))
 

	
 
    rmap.connect('changeset_comment',
 
                 '/{repo_name:.*}/changeset/{revision}/comment',
 
                controller='changeset', revision='tip', action='comment',
 
                conditions=dict(function=check_repo))
 

	
 
    rmap.connect('changeset_comment_delete',
 
                 '/{repo_name:.*}/changeset/comment/{comment_id}/delete',
 
                controller='changeset', action='delete_comment',
 
                conditions=dict(function=check_repo, method=["DELETE"]))
 

	
 
    rmap.connect('raw_changeset_home',
 
                 '/{repo_name:.*}/raw-changeset/{revision}',
 
                 controller='changeset', action='raw_changeset',
 
                 revision='tip', conditions=dict(function=check_repo))
 

	
 
    rmap.connect('summary_home', '/{repo_name:.*}/summary',
 
                controller='summary', conditions=dict(function=check_repo))
 

	
 
    rmap.connect('shortlog_home', '/{repo_name:.*}/shortlog',
 
                controller='shortlog', conditions=dict(function=check_repo))
 

	
 
    rmap.connect('branches_home', '/{repo_name:.*}/branches',
 
                controller='branches', conditions=dict(function=check_repo))
 

	
 
    rmap.connect('tags_home', '/{repo_name:.*}/tags',
 
                controller='tags', conditions=dict(function=check_repo))
 

	
 
    rmap.connect('bookmarks_home', '/{repo_name:.*}/bookmarks',
 
                controller='bookmarks', conditions=dict(function=check_repo))
 

	
 
    rmap.connect('changelog_home', '/{repo_name:.*}/changelog',
 
                controller='changelog', conditions=dict(function=check_repo))
 

	
 
    rmap.connect('changelog_details', '/{repo_name:.*}/changelog_details/{cs}',
 
                controller='changelog', action='changelog_details',
 
                conditions=dict(function=check_repo))
 

	
 
    rmap.connect('files_home', '/{repo_name:.*}/files/{revision}/{f_path:.*}',
 
                controller='files', revision='tip', f_path='',
 
                conditions=dict(function=check_repo))
 

	
 
    rmap.connect('files_diff_home', '/{repo_name:.*}/diff/{f_path:.*}',
 
                controller='files', action='diff', revision='tip', f_path='',
 
                conditions=dict(function=check_repo))
 

	
 
    rmap.connect('files_rawfile_home',
 
                 '/{repo_name:.*}/rawfile/{revision}/{f_path:.*}',
 
                 controller='files', action='rawfile', revision='tip',
 
                 f_path='', conditions=dict(function=check_repo))
 

	
 
    rmap.connect('files_raw_home',
 
                 '/{repo_name:.*}/raw/{revision}/{f_path:.*}',
 
                 controller='files', action='raw', revision='tip', f_path='',
 
                 conditions=dict(function=check_repo))
 

	
 
    rmap.connect('files_annotate_home',
 
                 '/{repo_name:.*}/annotate/{revision}/{f_path:.*}',
 
                 controller='files', action='annotate', revision='tip',
 
                 f_path='', conditions=dict(function=check_repo))
 
                 controller='files', action='index', revision='tip',
 
                 f_path='', annotate=True, conditions=dict(function=check_repo))
 

	
 
    rmap.connect('files_edit_home',
 
                 '/{repo_name:.*}/edit/{revision}/{f_path:.*}',
 
                 controller='files', action='edit', revision='tip',
 
                 f_path='', conditions=dict(function=check_repo))
 

	
 
    rmap.connect('files_add_home',
 
                 '/{repo_name:.*}/add/{revision}/{f_path:.*}',
 
                 controller='files', action='add', revision='tip',
 
                 f_path='', conditions=dict(function=check_repo))
 

	
 
    rmap.connect('files_archive_home', '/{repo_name:.*}/archive/{fname}',
 
                controller='files', action='archivefile',
 
                conditions=dict(function=check_repo))
 

	
 
    rmap.connect('files_nodelist_home',
 
                 '/{repo_name:.*}/nodelist/{revision}/{f_path:.*}',
 
                controller='files', action='nodelist',
 
                conditions=dict(function=check_repo))
 

	
 
    rmap.connect('repo_settings_delete', '/{repo_name:.*}/settings',
 
                controller='settings', action="delete",
 
                conditions=dict(method=["DELETE"], function=check_repo))
 

	
 
    rmap.connect('repo_settings_update', '/{repo_name:.*}/settings',
 
                controller='settings', action="update",
 
                conditions=dict(method=["PUT"], function=check_repo))
 

	
 
    rmap.connect('repo_settings_home', '/{repo_name:.*}/settings',
 
                controller='settings', action='index',
 
                conditions=dict(function=check_repo))
 

	
 
    rmap.connect('repo_fork_create_home', '/{repo_name:.*}/fork',
 
                controller='forks', action='fork_create',
 
                conditions=dict(function=check_repo, method=["POST"]))
 

	
 
    rmap.connect('repo_fork_home', '/{repo_name:.*}/fork',
 
                controller='forks', action='fork',
 
                conditions=dict(function=check_repo))
 

	
 
    rmap.connect('repo_forks_home', '/{repo_name:.*}/forks',
 
                 controller='forks', action='forks',
 
                 conditions=dict(function=check_repo))
 

	
 
    rmap.connect('repo_followers_home', '/{repo_name:.*}/followers',
 
                 controller='followers', action='followers',
 
                 conditions=dict(function=check_repo))
 

	
 
    return rmap
rhodecode/controllers/admin/ldap_settings.py
Show inline comments
 
@@ -7,131 +7,143 @@
 

	
 
    :created_on: Nov 26, 2010
 
    :author: marcink
 
    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
 
    :license: GPLv3, see COPYING for more details.
 
"""
 
# This program is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# This program is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
import logging
 
import formencode
 
import traceback
 

	
 
from formencode import htmlfill
 

	
 
from pylons import request, response, session, tmpl_context as c, url
 
from pylons.controllers.util import abort, redirect
 
from pylons.i18n.translation import _
 

	
 
from sqlalchemy.exc import DatabaseError
 

	
 
from rhodecode.lib.base import BaseController, render
 
from rhodecode.lib import helpers as h
 
from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
 
from rhodecode.lib.exceptions import LdapImportError
 
from rhodecode.model.forms import LdapSettingsForm
 
from rhodecode.model.db import RhodeCodeSetting
 

	
 
log = logging.getLogger(__name__)
 

	
 

	
 
class LdapSettingsController(BaseController):
 

	
 
    search_scope_choices = [('BASE', _('BASE'),),
 
                            ('ONELEVEL', _('ONELEVEL'),),
 
                            ('SUBTREE', _('SUBTREE'),),
 
                            ]
 
    search_scope_default = 'SUBTREE'
 

	
 
    tls_reqcert_choices = [('NEVER', _('NEVER'),),
 
                           ('ALLOW', _('ALLOW'),),
 
                           ('TRY', _('TRY'),),
 
                           ('DEMAND', _('DEMAND'),),
 
                           ('HARD', _('HARD'),),
 
                           ]
 
    tls_reqcert_default = 'DEMAND'
 

	
 
    tls_kind_choices = [('PLAIN', _('No encryption'),),
 
                        ('LDAPS', _('LDAPS connection'),),
 
                        ('START_TLS', _('START_TLS on LDAP connection'),)
 
                        ]
 

	
 
    tls_kind_default = 'PLAIN'
 

	
 
    @LoginRequired()
 
    @HasPermissionAllDecorator('hg.admin')
 
    def __before__(self):
 
        c.admin_user = session.get('admin_user')
 
        c.admin_username = session.get('admin_username')
 
        c.search_scope_choices = self.search_scope_choices
 
        c.tls_reqcert_choices = self.tls_reqcert_choices
 
        c.tls_kind_choices = self.tls_kind_choices
 

	
 
        c.search_scope_cur = self.search_scope_default
 
        c.tls_reqcert_cur = self.tls_reqcert_default
 
        c.tls_kind_cur = self.tls_kind_default
 

	
 
        super(LdapSettingsController, self).__before__()
 

	
 
    def index(self):
 
        defaults = RhodeCodeSetting.get_ldap_settings()
 
        c.search_scope_cur = defaults.get('ldap_search_scope')
 
        c.tls_reqcert_cur = defaults.get('ldap_tls_reqcert')
 
        c.tls_kind_cur = defaults.get('ldap_tls_kind')
 

	
 
        return htmlfill.render(
 
                    render('admin/ldap/ldap.html'),
 
                    defaults=defaults,
 
                    encoding="UTF-8",
 
                    force_defaults=True,)
 

	
 
    def ldap_settings(self):
 
        """POST ldap create and store ldap settings"""
 

	
 
        _form = LdapSettingsForm([x[0] for x in self.tls_reqcert_choices],
 
                                 [x[0] for x in self.search_scope_choices],
 
                                 [x[0] for x in self.tls_kind_choices])()
 
        # check the ldap lib
 
        ldap_active = False
 
        try:
 
            import ldap
 
            ldap_active = True
 
        except ImportError:
 
            pass
 

	
 
        try:
 
            form_result = _form.to_python(dict(request.POST))
 

	
 
            try:
 

	
 
                for k, v in form_result.items():
 
                    if k.startswith('ldap_'):
 
                        if k == 'ldap_active':
 
                            v = ldap_active
 
                        setting = RhodeCodeSetting.get_by_name(k)
 
                        setting.app_settings_value = v
 
                        self.sa.add(setting)
 

	
 
                self.sa.commit()
 
                h.flash(_('Ldap settings updated successfully'),
 
                    category='success')
 
                if not ldap_active:
 
                    #if ldap is missing send an info to user
 
                    h.flash(_('Unable to activate ldap. The "python-ldap" library '
 
                              'is missing.'), category='warning')
 

	
 
            except (DatabaseError,):
 
                raise
 
        except LdapImportError:
 
            h.flash(_('Unable to activate ldap. The "python-ldap" library '
 
                      'is missing.'), category='warning')
 

	
 
        except formencode.Invalid, errors:
 
            e = errors.error_dict or {}
 

	
 
            return htmlfill.render(
 
                render('admin/ldap/ldap.html'),
 
                defaults=errors.value,
 
                errors=e,
 
                prefix_error=False,
 
                encoding="UTF-8")
 
        except Exception:
 
            log.error(traceback.format_exc())
 
            h.flash(_('error occurred during update of ldap settings'),
 
                    category='error')
 

	
 
        return redirect(url('ldap_home'))
rhodecode/controllers/admin/notifications.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
"""
 
    rhodecode.controllers.admin.notifications
 
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 

	
 
    notifications controller for RhodeCode
 

	
 
    :created_on: Nov 23, 2010
 
    :author: marcink
 
    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
 
    :license: GPLv3, see COPYING for more details.
 
"""
 
# This program is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# This program is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 

	
 
import logging
 
import traceback
 

	
 
from pylons import request
 
from pylons import tmpl_context as c, url
 
from pylons.controllers.util import redirect
 

	
 
from webhelpers.paginate import Page
 

	
 
from rhodecode.lib.base import BaseController, render
 
from rhodecode.model.db import Notification
 

	
 
from rhodecode.model.notification import NotificationModel
 
from rhodecode.lib.auth import LoginRequired, NotAnonymous
 
from rhodecode.lib import helpers as h
 
from rhodecode.model.meta import Session
 

	
 

	
 
log = logging.getLogger(__name__)
 

	
 

	
 
class NotificationsController(BaseController):
 
    """REST Controller styled on the Atom Publishing Protocol"""
 
    # To properly map this controller, ensure your config/routing.py
 
    # file has a resource setup:
 
    #     map.resource('notification', 'notifications', controller='_admin/notifications',
 
    #         path_prefix='/_admin', name_prefix='_admin_')
 

	
 
    @LoginRequired()
 
    @NotAnonymous()
 
    def __before__(self):
 
        super(NotificationsController, self).__before__()
 

	
 
    def index(self, format='html'):
 
        """GET /_admin/notifications: All items in the collection"""
 
        # url('notifications')
 
        c.user = self.rhodecode_user
 
        c.notifications = NotificationModel()\
 
                            .get_for_user(self.rhodecode_user.user_id)
 
        notif = NotificationModel().get_for_user(self.rhodecode_user.user_id)
 
        p = int(request.params.get('page', 1))
 
        c.notifications = Page(notif, page=p, items_per_page=10)
 
        return render('admin/notifications/notifications.html')
 

	
 
    def mark_all_read(self):
 
        if request.environ.get('HTTP_X_PARTIAL_XHR'):
 
            nm = NotificationModel()
 
            # mark all read
 
            nm.mark_all_read_for_user(self.rhodecode_user.user_id)
 
            Session.commit()
 
            c.user = self.rhodecode_user
 
            c.notifications = nm.get_for_user(self.rhodecode_user.user_id)
 
            notif = nm.get_for_user(self.rhodecode_user.user_id)
 
            c.notifications = Page(notif, page=1, items_per_page=10)
 
            return render('admin/notifications/notifications_data.html')
 

	
 
    def create(self):
 
        """POST /_admin/notifications: Create a new item"""
 
        # url('notifications')
 

	
 
    def new(self, format='html'):
 
        """GET /_admin/notifications/new: Form to create a new item"""
 
        # url('new_notification')
 

	
 
    def update(self, notification_id):
 
        """PUT /_admin/notifications/id: Update an existing item"""
 
        # Forms posted to this method should contain a hidden field:
 
        #    <input type="hidden" name="_method" value="PUT" />
 
        # Or using helpers:
 
        #    h.form(url('notification', notification_id=ID),
 
        #           method='put')
 
        # url('notification', notification_id=ID)
 

	
 
    def delete(self, notification_id):
 
        """DELETE /_admin/notifications/id: Delete an existing item"""
 
        # Forms posted to this method should contain a hidden field:
 
        #    <input type="hidden" name="_method" value="DELETE" />
 
        # Or using helpers:
 
        #    h.form(url('notification', notification_id=ID),
 
        #           method='delete')
 
        # url('notification', notification_id=ID)
 

	
 
        try:
 
            no = Notification.get(notification_id)
 
            owner = lambda: (no.notifications_to_users.user.user_id
 
                             == c.rhodecode_user.user_id)
 
            if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
 
                    NotificationModel().delete(c.rhodecode_user.user_id, no)
 
                    Session.commit()
 
                    return 'ok'
 
        except Exception:
 
            Session.rollback()
 
            log.error(traceback.format_exc())
 
        return 'fail'
 

	
 
    def show(self, notification_id, format='html'):
 
        """GET /_admin/notifications/id: Show a specific item"""
 
        # url('notification', notification_id=ID)
 
        c.user = self.rhodecode_user
 
        no = Notification.get(notification_id)
 

	
 
        owner = lambda: (no.notifications_to_users.user.user_id
 
                         == c.user.user_id)
 
        if no and (h.HasPermissionAny('hg.admin', 'repository.admin')() or owner):
 
            unotification = NotificationModel()\
 
                            .get_user_notification(c.user.user_id, no)
 

	
 
            # if this association to user is not valid, we don't want to show
 
            # this message
 
            if unotification:
 
                if unotification.read is False:
 
                    unotification.mark_as_read()
 
                    Session.commit()
 
                c.notification = no
 

	
 
                return render('admin/notifications/show_notification.html')
 

	
 
        return redirect(url('notifications'))
 

	
 
    def edit(self, notification_id, format='html'):
 
        """GET /_admin/notifications/id/edit: Form to edit an existing item"""
 
        # url('edit_notification', notification_id=ID)
rhodecode/controllers/admin/settings.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
"""
 
    rhodecode.controllers.admin.settings
 
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 

	
 
    settings controller for rhodecode admin
 

	
 
    :created_on: Jul 14, 2010
 
    :author: marcink
 
    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
 
    :license: GPLv3, see COPYING for more details.
 
"""
 
# This program is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# This program is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 

	
 
import logging
 
import traceback
 
import formencode
 
import pkg_resources
 
import platform
 

	
 
from sqlalchemy import func
 
from formencode import htmlfill
 
from pylons import request, session, tmpl_context as c, url, config
 
from pylons.controllers.util import abort, redirect
 
from pylons.i18n.translation import _
 

	
 
from rhodecode.lib import helpers as h
 
from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
 
    HasPermissionAnyDecorator, NotAnonymous
 
from rhodecode.lib.base import BaseController, render
 
from rhodecode.lib.celerylib import tasks, run_task
 
from rhodecode.lib.utils import repo2db_mapper, invalidate_cache, \
 
    set_rhodecode_config, repo_name_slug
 
from rhodecode.model.db import RhodeCodeUi, Repository, RepoGroup, \
 
    RhodeCodeSetting
 
from rhodecode.model.forms import UserForm, ApplicationSettingsForm, \
 
    ApplicationUiSettingsForm
 
from rhodecode.model.scm import ScmModel
 
from rhodecode.model.user import UserModel
 
from rhodecode.model.db import User
 
from rhodecode.model.notification import EmailNotificationModel
 
from rhodecode.model.meta import Session
 

	
 
log = logging.getLogger(__name__)
 

	
 

	
 
class SettingsController(BaseController):
 
    """REST Controller styled on the Atom Publishing Protocol"""
 
    # To properly map this controller, ensure your config/routing.py
 
    # file has a resource setup:
 
    #     map.resource('setting', 'settings', controller='admin/settings',
 
    #         path_prefix='/admin', name_prefix='admin_')
 

	
 
    @LoginRequired()
 
    def __before__(self):
 
        c.admin_user = session.get('admin_user')
 
        c.admin_username = session.get('admin_username')
 
        c.modules = sorted([(p.project_name, p.version)
 
                            for p in pkg_resources.working_set],
 
                           key=lambda k: k[0].lower())
 
        c.py_version = platform.python_version()
 
        c.platform = platform.platform()
 
        super(SettingsController, self).__before__()
 

	
 
    @HasPermissionAllDecorator('hg.admin')
 
    def index(self, format='html'):
 
        """GET /admin/settings: All items in the collection"""
 
        # url('admin_settings')
 

	
 
        defaults = RhodeCodeSetting.get_app_settings()
 
        defaults.update(self.get_hg_ui_settings())
 

	
 
        return htmlfill.render(
 
            render('admin/settings/settings.html'),
 
            defaults=defaults,
 
            encoding="UTF-8",
 
            force_defaults=False
 
        )
 

	
 
    @HasPermissionAllDecorator('hg.admin')
 
    def create(self):
 
        """POST /admin/settings: Create a new item"""
 
        # url('admin_settings')
 

	
 
    @HasPermissionAllDecorator('hg.admin')
 
    def new(self, format='html'):
 
        """GET /admin/settings/new: Form to create a new item"""
 
        # url('admin_new_setting')
 

	
 
    @HasPermissionAllDecorator('hg.admin')
 
    def update(self, setting_id):
 
        """PUT /admin/settings/setting_id: Update an existing item"""
 
        # Forms posted to this method should contain a hidden field:
 
        #    <input type="hidden" name="_method" value="PUT" />
 
        # Or using helpers:
 
        #    h.form(url('admin_setting', setting_id=ID),
 
        #           method='put')
 
        # url('admin_setting', setting_id=ID)
 
        if setting_id == 'mapping':
 
            rm_obsolete = request.POST.get('destroy', False)
 
            log.debug('Rescanning directories with destroy=%s' % rm_obsolete)
 
            initial = ScmModel().repo_scan()
 
            log.debug('invalidating all repositories')
 
            for repo_name in initial.keys():
 
                invalidate_cache('get_repo_cached_%s' % repo_name)
 

	
 
            added, removed = repo2db_mapper(initial, rm_obsolete)
 

	
 
            h.flash(_('Repositories successfully'
 
                      ' rescanned added: %s,removed: %s') % (added, removed),
 
                    category='success')
 

	
 
        if setting_id == 'whoosh':
 
            repo_location = self.get_hg_ui_settings()['paths_root_path']
 
            full_index = request.POST.get('full_index', False)
 
            run_task(tasks.whoosh_index, repo_location, full_index)
 

	
 
            h.flash(_('Whoosh reindex task scheduled'), category='success')
 
        if setting_id == 'global':
 

	
 
            application_form = ApplicationSettingsForm()()
 
            try:
 
                form_result = application_form.to_python(dict(request.POST))
 

	
 
                try:
 
                    hgsettings1 = RhodeCodeSetting.get_by_name('title')
 
                    hgsettings1.app_settings_value = \
 
                        form_result['rhodecode_title']
 

	
 
                    hgsettings2 = RhodeCodeSetting.get_by_name('realm')
 
                    hgsettings2.app_settings_value = \
 
                        form_result['rhodecode_realm']
 

	
 
                    hgsettings3 = RhodeCodeSetting.get_by_name('ga_code')
 
                    hgsettings3.app_settings_value = \
 
                        form_result['rhodecode_ga_code']
 

	
 
                    self.sa.add(hgsettings1)
 
                    self.sa.add(hgsettings2)
 
                    self.sa.add(hgsettings3)
 
                    self.sa.commit()
 
                    set_rhodecode_config(config)
 
                    h.flash(_('Updated application settings'),
 
                            category='success')
 

	
 
                except Exception:
 
                    log.error(traceback.format_exc())
 
                    h.flash(_('error occurred during updating '
 
                              'application settings'),
 
                            category='error')
 

	
 
                    self.sa.rollback()
 

	
 
            except formencode.Invalid, errors:
 
                return htmlfill.render(
 
                     render('admin/settings/settings.html'),
 
                     defaults=errors.value,
 
                     errors=errors.error_dict or {},
 
                     prefix_error=False,
 
                     encoding="UTF-8")
 

	
 
        if setting_id == 'mercurial':
 
            application_form = ApplicationUiSettingsForm()()
 
            try:
 
                form_result = application_form.to_python(dict(request.POST))
 

	
 
                try:
 

	
rhodecode/controllers/changelog.py
Show inline comments
 
@@ -32,103 +32,104 @@ from pylons.controllers.util import redi
 
from pylons.i18n.translation import _
 

	
 
import rhodecode.lib.helpers as h
 
from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
 
from rhodecode.lib.base import BaseRepoController, render
 
from rhodecode.lib.helpers import RepoPage
 
from rhodecode.lib.compat import json
 

	
 
from rhodecode.lib.vcs.exceptions import RepositoryError, ChangesetDoesNotExistError
 
from rhodecode.model.db import Repository
 

	
 
log = logging.getLogger(__name__)
 

	
 

	
 
class ChangelogController(BaseRepoController):
 

	
 
    @LoginRequired()
 
    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
 
                                   'repository.admin')
 
    def __before__(self):
 
        super(ChangelogController, self).__before__()
 
        c.affected_files_cut_off = 60
 

	
 
    def index(self):
 
        limit = 100
 
        default = 20
 
        if request.params.get('size'):
 
            try:
 
                int_size = int(request.params.get('size'))
 
            except ValueError:
 
                int_size = default
 
            int_size = int_size if int_size <= limit else limit
 
            c.size = int_size
 
            session['changelog_size'] = c.size
 
            session.save()
 
        else:
 
            c.size = int(session.get('changelog_size', default))
 

	
 
        p = int(request.params.get('page', 1))
 
        branch_name = request.params.get('branch', None)
 
        try:
 
            if branch_name:
 
                collection = [z for z in
 
                              c.rhodecode_repo.get_changesets(start=0,
 
                                                    branch_name=branch_name)]
 
                c.total_cs = len(collection)
 
            else:
 
                collection = c.rhodecode_repo
 
                c.total_cs = len(c.rhodecode_repo)
 

	
 
            c.pagination = RepoPage(collection, page=p, item_count=c.total_cs,
 
                                    items_per_page=c.size, branch=branch_name)
 
            collection = list(c.pagination)
 
            page_revisions = [x.raw_id for x in collection]
 
            c.comments = c.rhodecode_db_repo.comments(page_revisions)
 

	
 
        except (RepositoryError, ChangesetDoesNotExistError, Exception), e:
 
            log.error(traceback.format_exc())
 
            h.flash(str(e), category='warning')
 
            return redirect(url('home'))
 

	
 
        self._graph(c.rhodecode_repo, collection, c.total_cs, c.size, p)
 

	
 
        c.branch_name = branch_name
 
        c.branch_filters = [('', _('All Branches'))] + \
 
            [(k, k) for k in c.rhodecode_repo.branches.keys()]
 

	
 
        return render('changelog/changelog.html')
 

	
 
    def changelog_details(self, cs):
 
        if request.environ.get('HTTP_X_PARTIAL_XHR'):
 
            c.cs = c.rhodecode_repo.get_changeset(cs)
 
            return render('changelog/changelog_details.html')
 

	
 
    def _graph(self, repo, collection, repo_size, size, p):
 
        """
 
        Generates a DAG graph for mercurial
 

	
 
        :param repo: repo instance
 
        :param size: number of commits to show
 
        :param p: page number
 
        """
 
        if not collection:
 
            c.jsdata = json.dumps([])
 
            return
 

	
 
        data = []
 
        revs = [x.revision for x in collection]
 

	
 
        if repo.alias == 'git':
 
            for _ in revs:
 
                vtx = [0, 1]
 
                edges = [[0, 0, 1]]
 
                data.append(['', vtx, edges])
 

	
 
        elif repo.alias == 'hg':
 
            c.dag = graphmod.colored(graphmod.dagwalker(repo._repo, revs))
 
            dag = graphmod.dagwalker(repo._repo, revs)
 
            c.dag = graphmod.colored(dag, repo._repo)
 
            for (id, type, ctx, vtx, edges) in c.dag:
 
                if type != graphmod.CHANGESET:
 
                    continue
 
                data.append(['', vtx, edges])
 

	
 
        c.jsdata = json.dumps(data)
rhodecode/controllers/changeset.py
Show inline comments
 
@@ -266,117 +266,132 @@ class ChangesetController(BaseRepoContro
 
                    filenode_new=node,
 
                    cut_off_limit=lim,
 
                    ignore_whitespace=ign_whitespace_lcl,
 
                    line_context=line_context_lcl,
 
                    enable_comments=enable_comments
 
                )
 
                cumulative_diff += size
 
                c.lines_added += st[0]
 
                c.lines_deleted += st[1]
 
                c.changes[changeset.raw_id].append(
 
                    ('changed', node, diff, cs1, cs2, st)
 
                )
 
            #==================================================================
 
            # REMOVED FILES
 
            #==================================================================
 
            for node in changeset.removed:
 
                c.changes[changeset.raw_id].append(
 
                    ('removed', node, None, None, None, (0, 0))
 
                )
 

	
 
        # count inline comments
 
        for path, lines in c.inline_comments:
 
            for comments in lines.values():
 
                c.inline_cnt += len(comments)
 

	
 
        if len(c.cs_ranges) == 1:
 
            c.changeset = c.cs_ranges[0]
 
            c.changes = c.changes[c.changeset.raw_id]
 

	
 
            return render('changeset/changeset.html')
 
        else:
 
            return render('changeset/changeset_range.html')
 

	
 
    def raw_changeset(self, revision):
 

	
 
        method = request.GET.get('diff', 'show')
 
        ignore_whitespace = request.GET.get('ignorews') == '1'
 
        line_context = request.GET.get('context', 3)
 
        try:
 
            c.scm_type = c.rhodecode_repo.alias
 
            c.changeset = c.rhodecode_repo.get_changeset(revision)
 
        except RepositoryError:
 
            log.error(traceback.format_exc())
 
            return redirect(url('home'))
 
        else:
 
            try:
 
                c.changeset_parent = c.changeset.parents[0]
 
            except IndexError:
 
                c.changeset_parent = None
 
            c.changes = []
 

	
 
            for node in c.changeset.added:
 
                filenode_old = FileNode(node.path, '')
 
                if filenode_old.is_binary or node.is_binary:
 
                    diff = _('binary file') + '\n'
 
                else:
 
                    f_gitdiff = diffs.get_gitdiff(filenode_old, node,
 
                                           ignore_whitespace=ignore_whitespace,
 
                                           context=line_context)
 
                    diff = diffs.DiffProcessor(f_gitdiff,
 
                                                format='gitdiff').raw_diff()
 

	
 
                cs1 = None
 
                cs2 = node.changeset.raw_id
 
                c.changes.append(('added', node, diff, cs1, cs2))
 

	
 
            for node in c.changeset.changed:
 
                filenode_old = c.changeset_parent.get_node(node.path)
 
                if filenode_old.is_binary or node.is_binary:
 
                    diff = _('binary file')
 
                else:
 
                    f_gitdiff = diffs.get_gitdiff(filenode_old, node,
 
                                           ignore_whitespace=ignore_whitespace,
 
                                           context=line_context)
 
                    diff = diffs.DiffProcessor(f_gitdiff,
 
                                                format='gitdiff').raw_diff()
 

	
 
                cs1 = filenode_old.changeset.raw_id
 
                cs2 = node.changeset.raw_id
 
                c.changes.append(('changed', node, diff, cs1, cs2))
 

	
 
        response.content_type = 'text/plain'
 

	
 
        if method == 'download':
 
            response.content_disposition = 'attachment; filename=%s.patch' \
 
                                            % revision
 

	
 
        c.parent_tmpl = ''.join(['# Parent  %s\n' % x.raw_id
 
                                 for x in c.changeset.parents])
 

	
 
        c.diffs = ''
 
        for x in c.changes:
 
            c.diffs += x[2]
 

	
 
        return render('changeset/raw_changeset.html')
 

	
 
    @jsonify
 
    def comment(self, repo_name, revision):
 
        ChangesetCommentsModel().create(text=request.POST.get('text'),
 
        comm = ChangesetCommentsModel().create(
 
            text=request.POST.get('text'),
 
                                        repo_id=c.rhodecode_db_repo.repo_id,
 
                                        user_id=c.rhodecode_user.user_id,
 
                                        revision=revision,
 
                                        f_path=request.POST.get('f_path'),
 
                                        line_no=request.POST.get('line'))
 
            line_no=request.POST.get('line')
 
        )
 
        Session.commit()
 
        if not request.environ.get('HTTP_X_PARTIAL_XHR'):
 
        return redirect(h.url('changeset_home', repo_name=repo_name,
 
                              revision=revision))
 

	
 
        data = {
 
           'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
 
        }
 
        if comm:
 
            c.co = comm
 
            data.update(comm.get_dict())
 
            data.update({'rendered_text':
 
                         render('changeset/changeset_comment_block.html')})
 

	
 
        return data
 

	
 
    @jsonify
 
    def delete_comment(self, repo_name, comment_id):
 
        co = ChangesetComment.get(comment_id)
 
        owner = lambda: co.author.user_id == c.rhodecode_user.user_id
 
        if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
 
            ChangesetCommentsModel().delete(comment=co)
 
            Session.commit()
 
            return True
 
        else:
 
            raise HTTPForbidden()
rhodecode/controllers/files.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
"""
 
    rhodecode.controllers.files
 
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~
 

	
 
    Files controller for RhodeCode
 

	
 
    :created_on: Apr 21, 2010
 
    :author: marcink
 
    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
 
    :license: GPLv3, see COPYING for more details.
 
"""
 
# This program is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# This program is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 

	
 
import os
 
import logging
 
import traceback
 

	
 
from pylons import request, response, tmpl_context as c, url
 
from pylons.i18n.translation import _
 
from pylons.controllers.util import redirect
 
from pylons.decorators import jsonify
 

	
 
from rhodecode.lib import diffs
 
from rhodecode.lib import helpers as h
 

	
 
from rhodecode.lib.compat import OrderedDict
 
from rhodecode.lib.utils2 import convert_line_endings, detect_mode, safe_str
 
from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
 
from rhodecode.lib.base import BaseRepoController, render
 
from rhodecode.lib.utils import EmptyChangeset
 
from rhodecode.lib.vcs.conf import settings
 
from rhodecode.lib.vcs.exceptions import RepositoryError, \
 
    ChangesetDoesNotExistError, EmptyRepositoryError, \
 
    ImproperArchiveTypeError, VCSError, NodeAlreadyExistsError
 
from rhodecode.lib.vcs.nodes import FileNode
 

	
 
from rhodecode.model.repo import RepoModel
 
from rhodecode.model.scm import ScmModel
 
from rhodecode.model.db import Repository
 

	
 
from rhodecode.controllers.changeset import anchor_url, _ignorews_url,\
 
    _context_url, get_line_ctx, get_ignore_ws
 

	
 

	
 
log = logging.getLogger(__name__)
 

	
 

	
 
class FilesController(BaseRepoController):
 

	
 
    @LoginRequired()
 
    def __before__(self):
 
        super(FilesController, self).__before__()
 
        c.cut_off_limit = self.cut_off_limit
 

	
 
    def __get_cs_or_redirect(self, rev, repo_name, redirect_after=True):
 
        """
 
        Safe way to get changeset if error occur it redirects to tip with
 
        proper message
 

	
 
        :param rev: revision to fetch
 
        :param repo_name: repo name to redirect after
 
        """
 

	
 
        try:
 
            return c.rhodecode_repo.get_changeset(rev)
 
        except EmptyRepositoryError, e:
 
            if not redirect_after:
 
                return None
 
            url_ = url('files_add_home',
 
                       repo_name=c.repo_name,
 
                       revision=0, f_path='')
 
            add_new = '<a href="%s">[%s]</a>' % (url_, _('add new'))
 
            h.flash(h.literal(_('There are no files yet %s' % add_new)),
 
                    category='warning')
 
            redirect(h.url('summary_home', repo_name=repo_name))
 

	
 
        except RepositoryError, e:
 
            h.flash(str(e), category='warning')
 
            redirect(h.url('files_home', repo_name=repo_name, revision='tip'))
 

	
 
    def __get_filenode_or_redirect(self, repo_name, cs, path):
 
        """
 
        Returns file_node, if error occurs or given path is directory,
 
        it'll redirect to top level path
 

	
 
        :param repo_name: repo_name
 
        :param cs: given changeset
 
        :param path: path to lookup
 
        """
 

	
 
        try:
 
            file_node = cs.get_node(path)
 
            if file_node.is_dir():
 
                raise RepositoryError('given path is a directory')
 
        except RepositoryError, e:
 
            h.flash(str(e), category='warning')
 
            redirect(h.url('files_home', repo_name=repo_name,
 
                           revision=cs.raw_id))
 

	
 
        return file_node
 

	
 
    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
 
                                   'repository.admin')
 
    def index(self, repo_name, revision, f_path):
 
    def index(self, repo_name, revision, f_path, annotate=False):
 
        # redirect to given revision from form if given
 
        post_revision = request.POST.get('at_rev', None)
 
        if post_revision:
 
            cs = self.__get_cs_or_redirect(post_revision, repo_name)
 
            redirect(url('files_home', repo_name=c.repo_name,
 
                         revision=cs.raw_id, f_path=f_path))
 

	
 
        c.changeset = self.__get_cs_or_redirect(revision, repo_name)
 
        c.branch = request.GET.get('branch', None)
 
        c.f_path = f_path
 

	
 
        c.annotate = annotate
 
        cur_rev = c.changeset.revision
 

	
 
        # prev link
 
        try:
 
            prev_rev = c.rhodecode_repo.get_changeset(cur_rev).prev(c.branch)
 
            c.url_prev = url('files_home', repo_name=c.repo_name,
 
                         revision=prev_rev.raw_id, f_path=f_path)
 
            if c.branch:
 
                c.url_prev += '?branch=%s' % c.branch
 
        except (ChangesetDoesNotExistError, VCSError):
 
            c.url_prev = '#'
 

	
 
        # next link
 
        try:
 
            next_rev = c.rhodecode_repo.get_changeset(cur_rev).next(c.branch)
 
            c.url_next = url('files_home', repo_name=c.repo_name,
 
                     revision=next_rev.raw_id, f_path=f_path)
 
            if c.branch:
 
                c.url_next += '?branch=%s' % c.branch
 
        except (ChangesetDoesNotExistError, VCSError):
 
            c.url_next = '#'
 

	
 
        # files or dirs
 
        try:
 
            c.file = c.changeset.get_node(f_path)
 

	
 
            if c.file.is_file():
 
                c.file_history = self._get_node_history(c.changeset, f_path)
 
            else:
 
                c.file_history = []
 
        except RepositoryError, e:
 
            h.flash(str(e), category='warning')
 
            redirect(h.url('files_home', repo_name=repo_name,
 
                           revision=revision))
 

	
 
        return render('files/files.html')
 

	
 
    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
 
                                   'repository.admin')
 
    def rawfile(self, repo_name, revision, f_path):
 
        cs = self.__get_cs_or_redirect(revision, repo_name)
 
        file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
 

	
 
        response.content_disposition = 'attachment; filename=%s' % \
 
            safe_str(f_path.split(os.sep)[-1])
 
            safe_str(f_path.split(Repository.url_sep())[-1])
 

	
 
        response.content_type = file_node.mimetype
 
        return file_node.content
 

	
 
    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
 
                                   'repository.admin')
 
    def raw(self, repo_name, revision, f_path):
 
        cs = self.__get_cs_or_redirect(revision, repo_name)
 
        file_node = self.__get_filenode_or_redirect(repo_name, cs, f_path)
 

	
 
        raw_mimetype_mapping = {
 
            # map original mimetype to a mimetype used for "show as raw"
 
            # you can also provide a content-disposition to override the
 
            # default "attachment" disposition.
 
            # orig_type: (new_type, new_dispo)
 

	
 
            # show images inline:
 
            'image/x-icon': ('image/x-icon', 'inline'),
 
            'image/png': ('image/png', 'inline'),
 
            'image/gif': ('image/gif', 'inline'),
 
            'image/jpeg': ('image/jpeg', 'inline'),
 
            'image/svg+xml': ('image/svg+xml', 'inline'),
 
        }
 

	
 
        mimetype = file_node.mimetype
 
        try:
 
            mimetype, dispo = raw_mimetype_mapping[mimetype]
 
        except KeyError:
 
            # we don't know anything special about this, handle it safely
 
            if file_node.is_binary:
 
                # do same as download raw for binary files
 
                mimetype, dispo = 'application/octet-stream', 'attachment'
 
            else:
 
                # do not just use the original mimetype, but force text/plain,
 
                # otherwise it would serve text/html and that might be unsafe.
 
                # Note: underlying vcs library fakes text/plain mimetype if the
 
                # mimetype can not be determined and it thinks it is not
 
                # binary.This might lead to erroneous text display in some
 
                # cases, but helps in other cases, like with text files
 
                # without extension.
 
                mimetype, dispo = 'text/plain', 'inline'
 

	
 
        if dispo == 'attachment':
 
            dispo = 'attachment; filename=%s' % \
 
                        safe_str(f_path.split(os.sep)[-1])
 

	
 
        response.content_disposition = dispo
 
        response.content_type = mimetype
 
        return file_node.content
 

	
 
    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
 
                                   'repository.admin')
 
    def annotate(self, repo_name, revision, f_path):
 
        c.cs = self.__get_cs_or_redirect(revision, repo_name)
 
        c.file = self.__get_filenode_or_redirect(repo_name, c.cs, f_path)
 

	
 
        c.file_history = self._get_node_history(c.cs, f_path)
 
        c.f_path = f_path
 
        return render('files/files_annotate.html')
 

	
 
    @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
 
    def edit(self, repo_name, revision, f_path):
 
        r_post = request.POST
 

	
 
        c.cs = self.__get_cs_or_redirect(revision, repo_name)
 
        c.file = self.__get_filenode_or_redirect(repo_name, c.cs, f_path)
 

	
 
        if c.file.is_binary:
 
            return redirect(url('files_home', repo_name=c.repo_name,
 
                         revision=c.cs.raw_id, f_path=f_path))
 

	
 
        c.f_path = f_path
 

	
 
        if r_post:
 

	
 
            old_content = c.file.content
 
            sl = old_content.splitlines(1)
 
            first_line = sl[0] if sl else ''
 
            # modes:  0 - Unix, 1 - Mac, 2 - DOS
 
            mode = detect_mode(first_line, 0)
 
            content = convert_line_endings(r_post.get('content'), mode)
 

	
 
            message = r_post.get('message') or (_('Edited %s via RhodeCode')
 
                                                % (f_path))
 
            author = self.rhodecode_user.full_contact
 

	
 
            if content == old_content:
 
                h.flash(_('No changes'),
 
                    category='warning')
 
                return redirect(url('changeset_home', repo_name=c.repo_name,
 
                                    revision='tip'))
 

	
 
            try:
 
                self.scm_model.commit_change(repo=c.rhodecode_repo,
 
                                             repo_name=repo_name, cs=c.cs,
 
                                             user=self.rhodecode_user,
 
                                             author=author, message=message,
 
                                             content=content, f_path=f_path)
 
                h.flash(_('Successfully committed to %s' % f_path),
 
                        category='success')
 

	
 
            except Exception:
 
                log.error(traceback.format_exc())
 
                h.flash(_('Error occurred during commit'), category='error')
 
            return redirect(url('changeset_home',
 
                                repo_name=c.repo_name, revision='tip'))
 

	
 
        return render('files/files_edit.html')
 

	
 
    @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
 
    def add(self, repo_name, revision, f_path):
 
        r_post = request.POST
 
        c.cs = self.__get_cs_or_redirect(revision, repo_name,
 
                                         redirect_after=False)
 
        if c.cs is None:
 
            c.cs = EmptyChangeset(alias=c.rhodecode_repo.alias)
 

	
 
        c.f_path = f_path
 

	
 
        if r_post:
 
            unix_mode = 0
 
            content = convert_line_endings(r_post.get('content'), unix_mode)
 

	
 
            message = r_post.get('message') or (_('Added %s via RhodeCode')
 
                                                % (f_path))
 
            location = r_post.get('location')
 
            filename = r_post.get('filename')
 
            file_obj = r_post.get('upload_file', None)
 

	
 
            if file_obj is not None and hasattr(file_obj, 'filename'):
 
                filename = file_obj.filename
 
                content = file_obj.file
 

	
 
            node_path = os.path.join(location, filename)
 
            author = self.rhodecode_user.full_contact
 

	
 
            if not content:
 
                h.flash(_('No content'), category='warning')
 
                return redirect(url('changeset_home', repo_name=c.repo_name,
 
                                    revision='tip'))
 
            if not filename:
 
                h.flash(_('No filename'), category='warning')
 
                return redirect(url('changeset_home', repo_name=c.repo_name,
 
                                    revision='tip'))
 

	
 
            try:
 
                self.scm_model.create_node(repo=c.rhodecode_repo,
 
                                             repo_name=repo_name, cs=c.cs,
 
                                             user=self.rhodecode_user,
 
                                             author=author, message=message,
 
                                             content=content, f_path=node_path)
 
                h.flash(_('Successfully committed to %s' % node_path),
 
                        category='success')
 
            except NodeAlreadyExistsError, e:
 
                h.flash(_(e), category='error')
 
            except Exception:
rhodecode/controllers/forks.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
"""
 
    rhodecode.controllers.forks
 
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 

	
 
    forks controller for rhodecode
 

	
 
    :created_on: Apr 23, 2011
 
    :author: marcink
 
    :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
 
    :license: GPLv3, see COPYING for more details.
 
"""
 
# This program is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# This program is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
import logging
 
import formencode
 
import traceback
 
from formencode import htmlfill
 

	
 
from pylons import tmpl_context as c, request, url
 
from pylons.controllers.util import redirect
 
from pylons.i18n.translation import _
 

	
 
import rhodecode.lib.helpers as h
 

	
 
from rhodecode.lib.helpers import Page
 
from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator, \
 
    NotAnonymous
 
    NotAnonymous, HasRepoPermissionAny
 
from rhodecode.lib.base import BaseRepoController, render
 
from rhodecode.model.db import Repository, RepoGroup, UserFollowing, User
 
from rhodecode.model.repo import RepoModel
 
from rhodecode.model.forms import RepoForkForm
 

	
 
log = logging.getLogger(__name__)
 

	
 

	
 
class ForksController(BaseRepoController):
 

	
 
    @LoginRequired()
 
    def __before__(self):
 
        super(ForksController, self).__before__()
 

	
 
    def __load_defaults(self):
 
        c.repo_groups = RepoGroup.groups_choices()
 
        c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
 

	
 
    def __load_data(self, repo_name=None):
 
        """
 
        Load defaults settings for edit, and update
 

	
 
        :param repo_name:
 
        """
 
        self.__load_defaults()
 

	
 
        c.repo_info = db_repo = Repository.get_by_repo_name(repo_name)
 
        repo = db_repo.scm_instance
 

	
 
        if c.repo_info is None:
 
            h.flash(_('%s repository is not mapped to db perhaps'
 
                      ' it was created or renamed from the filesystem'
 
                      ' please run the application again'
 
                      ' in order to rescan repositories') % repo_name,
 
                      category='error')
 

	
 
            return redirect(url('repos'))
 

	
 
        c.default_user_id = User.get_by_username('default').user_id
 
        c.in_public_journal = UserFollowing.query()\
 
            .filter(UserFollowing.user_id == c.default_user_id)\
 
            .filter(UserFollowing.follows_repository == c.repo_info).scalar()
 

	
 
        if c.repo_info.stats:
 
            last_rev = c.repo_info.stats.stat_on_revision+1
 
        else:
 
            last_rev = 0
 
        c.stats_revision = last_rev
 

	
 
        c.repo_last_rev = repo.count() if repo.revisions else 0
 

	
 
        if last_rev == 0 or c.repo_last_rev == 0:
 
            c.stats_percentage = 0
 
        else:
 
            c.stats_percentage = '%.2f' % ((float((last_rev)) /
 
                                            c.repo_last_rev) * 100)
 

	
 
        defaults = RepoModel()._get_defaults(repo_name)
 
        # add prefix to fork
 
        defaults['repo_name'] = 'fork-' + defaults['repo_name']
 
        return defaults
 

	
 
    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
 
                                   'repository.admin')
 
    def forks(self, repo_name):
 
        p = int(request.params.get('page', 1))
 
        repo_id = c.rhodecode_db_repo.repo_id
 
        d = Repository.get_repo_forks(repo_id)
 
        d = []
 
        for r in Repository.get_repo_forks(repo_id):
 
            if not HasRepoPermissionAny(
 
                'repository.read', 'repository.write', 'repository.admin'
 
            )(r.repo_name, 'get forks check'):
 
                continue
 
            d.append(r)
 
        c.forks_pager = Page(d, page=p, items_per_page=20)
 

	
 
        c.forks_data = render('/forks/forks_data.html')
 

	
 
        if request.environ.get('HTTP_X_PARTIAL_XHR'):
 
            return c.forks_data
 

	
 
        return render('/forks/forks.html')
 

	
 
    @NotAnonymous()
 
    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
 
                                   'repository.admin')
 
    def fork(self, repo_name):
 
        c.repo_info = Repository.get_by_repo_name(repo_name)
 
        if not c.repo_info:
 
            h.flash(_('%s repository is not mapped to db perhaps'
 
                      ' it was created or renamed from the file system'
 
                      ' please run the application again'
 
                      ' in order to rescan repositories') % repo_name,
 
                      category='error')
 

	
 
            return redirect(url('home'))
 

	
 
        defaults = self.__load_data(repo_name)
 

	
 
        return htmlfill.render(
 
            render('forks/fork.html'),
 
            defaults=defaults,
 
            encoding="UTF-8",
 
            force_defaults=False
 
        )
 

	
 

	
 
    @NotAnonymous()
 
    @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
 
                                   'repository.admin')
 
    def fork_create(self, repo_name):
 
        self.__load_defaults()
 
        c.repo_info = Repository.get_by_repo_name(repo_name)
 
        _form = RepoForkForm(old_data={'repo_type': c.repo_info.repo_type},
 
                             repo_groups=c.repo_groups_choices,)()
 
        form_result = {}
 
        try:
 
            form_result = _form.to_python(dict(request.POST))
 
            # add org_path of repo so we can do a clone from it later
 
            form_result['org_path'] = c.repo_info.repo_name
 

	
 
            # create fork is done sometimes async on celery, db transaction
 
            # management is handled there.
 
            RepoModel().create_fork(form_result, self.rhodecode_user)
 
            h.flash(_('forked %s repository as %s') \
 
                      % (repo_name, form_result['repo_name']),
 
                    category='success')
 
        except formencode.Invalid, errors:
 
            c.new_repo = errors.value['repo_name']
 

	
 
            return htmlfill.render(
 
                render('forks/fork.html'),
 
                defaults=errors.value,
 
                errors=errors.error_dict or {},
 
                prefix_error=False,
 
                encoding="UTF-8")
 
        except Exception:
 
            log.error(traceback.format_exc())
 
            h.flash(_('An error occurred during repository forking %s') %
 
                    repo_name, category='error')
 

	
 
        return redirect(url('home'))
rhodecode/controllers/summary.py
Show inline comments
 
@@ -86,152 +86,155 @@ class SummaryController(BaseRepoControll
 
            username = str(self.rhodecode_user.username)
 
            password = '@'
 

	
 
        parsed_url = urlparse(url.current(qualified=True))
 

	
 
        default_clone_uri = '{scheme}://{user}{pass}{netloc}{path}'
 

	
 
        uri_tmpl = config.get('clone_uri', default_clone_uri)
 
        uri_tmpl = uri_tmpl.replace('{', '%(').replace('}', ')s')
 
        decoded_path = safe_unicode(urllib.unquote(parsed_url.path))
 
        uri_dict = {
 
           'user': username,
 
           'pass': password,
 
           'scheme': parsed_url.scheme,
 
           'netloc': parsed_url.netloc,
 
           'path': decoded_path
 
        }
 

	
 
        uri = uri_tmpl % uri_dict
 
        # generate another clone url by id
 
        uri_dict.update(
 
         {'path': decoded_path.replace(repo_name, '_%s' % c.dbrepo.repo_id)}
 
        )
 
        uri_id = uri_tmpl % uri_dict
 

	
 
        c.clone_repo_url = uri
 
        c.clone_repo_url_id = uri_id
 
        c.repo_tags = OrderedDict()
 
        for name, hash_ in c.rhodecode_repo.tags.items()[:10]:
 
            try:
 
                c.repo_tags[name] = c.rhodecode_repo.get_changeset(hash_)
 
            except ChangesetError:
 
                c.repo_tags[name] = EmptyChangeset(hash_)
 

	
 
        c.repo_branches = OrderedDict()
 
        for name, hash_ in c.rhodecode_repo.branches.items()[:10]:
 
            try:
 
                c.repo_branches[name] = c.rhodecode_repo.get_changeset(hash_)
 
            except ChangesetError:
 
                c.repo_branches[name] = EmptyChangeset(hash_)
 

	
 
        td = date.today() + timedelta(days=1)
 
        td_1m = td - timedelta(days=calendar.mdays[td.month])
 
        td_1y = td - timedelta(days=365)
 

	
 
        ts_min_m = mktime(td_1m.timetuple())
 
        ts_min_y = mktime(td_1y.timetuple())
 
        ts_max_y = mktime(td.timetuple())
 

	
 
        if dbrepo.enable_statistics:
 
            c.show_stats = True
 
            c.no_data_msg = _('No data loaded yet')
 
            run_task(get_commits_stats, c.dbrepo.repo_name, ts_min_y, ts_max_y)
 
        else:
 
            c.show_stats = False
 
            c.no_data_msg = _('Statistics are disabled for this repository')
 
        c.ts_min = ts_min_m
 
        c.ts_max = ts_max_y
 

	
 
        stats = self.sa.query(Statistics)\
 
            .filter(Statistics.repository == dbrepo)\
 
            .scalar()
 

	
 
        c.stats_percentage = 0
 

	
 
        if stats and stats.languages:
 
            c.no_data = False is dbrepo.enable_statistics
 
            lang_stats_d = json.loads(stats.languages)
 
            c.commit_data = stats.commit_activity
 
            c.overview_data = stats.commit_activity_combined
 

	
 
            lang_stats = ((x, {"count": y,
 
                               "desc": LANGUAGES_EXTENSIONS_MAP.get(x)})
 
                          for x, y in lang_stats_d.items())
 

	
 
            c.trending_languages = json.dumps(
 
                sorted(lang_stats, reverse=True, key=lambda k: k[1])[:10]
 
            )
 
            last_rev = stats.stat_on_revision + 1
 
            c.repo_last_rev = c.rhodecode_repo.count()\
 
                if c.rhodecode_repo.revisions else 0
 
            if last_rev == 0 or c.repo_last_rev == 0:
 
                pass
 
            else:
 
                c.stats_percentage = '%.2f' % ((float((last_rev)) /
 
                                                c.repo_last_rev) * 100)
 
        else:
 
            c.commit_data = json.dumps({})
 
            c.overview_data = json.dumps([[ts_min_y, 0], [ts_max_y, 10]])
 
            c.trending_languages = json.dumps({})
 
            c.no_data = True
 

	
 
        c.enable_downloads = dbrepo.enable_downloads
 
        if c.enable_downloads:
 
            c.download_options = self._get_download_links(c.rhodecode_repo)
 

	
 
        c.readme_data, c.readme_file = self.__get_readme_data(c.rhodecode_db_repo)
 
        c.readme_data, c.readme_file = self.__get_readme_data(
 
            c.rhodecode_db_repo.repo_name, c.rhodecode_repo
 
        )
 
        return render('summary/summary.html')
 

	
 
    def __get_readme_data(self, repo):
 
    def __get_readme_data(self, repo_name, repo):
 

	
 
        @cache_region('long_term')
 
        def _get_readme_from_cache(key):
 
            readme_data = None
 
            readme_file = None
 
            log.debug('Fetching readme file')
 
            try:
 
                cs = repo.get_changeset('tip')
 
                cs = repo.get_changeset()  # fetches TIP
 
                renderer = MarkupRenderer()
 
                for f in README_FILES:
 
                    try:
 
                        readme = cs.get_node(f)
 
                        readme_file = f
 
                        readme_data = renderer.render(readme.content, f)
 
                        log.debug('Found readme %s' % readme_file)
 
                        break
 
                    except NodeDoesNotExistError:
 
                        continue
 
            except ChangesetError:
 
                log.error(traceback.format_exc())
 
                pass
 
            except EmptyRepositoryError:
 
                pass
 
            except Exception:
 
                log.error(traceback.format_exc())
 

	
 
            return readme_data, readme_file
 

	
 
        key = repo.repo_name + '_README'
 
        key = repo_name + '_README'
 
        inv = CacheInvalidation.invalidate(key)
 
        if inv is not None:
 
            region_invalidate(_get_readme_from_cache, None, key)
 
            CacheInvalidation.set_valid(inv.cache_key)
 
        return _get_readme_from_cache(key)
 

	
 
    def _get_download_links(self, repo):
 

	
 
        download_l = []
 

	
 
        branches_group = ([], _("Branches"))
 
        tags_group = ([], _("Tags"))
 

	
 
        for name, chs in c.rhodecode_repo.branches.items():
 
            #chs = chs.split(':')[-1]
 
            branches_group[0].append((chs, name),)
 
        download_l.append(branches_group)
 

	
 
        for name, chs in c.rhodecode_repo.tags.items():
 
            #chs = chs.split(':')[-1]
 
            tags_group[0].append((chs, name),)
 
        download_l.append(tags_group)
 

	
 
        return download_l
rhodecode/lib/base.py
Show inline comments
 
@@ -23,180 +23,191 @@ from rhodecode.lib.auth import AuthUser,
 
from rhodecode.lib.utils import get_repo_slug, invalidate_cache
 
from rhodecode.model import meta
 

	
 
from rhodecode.model.db import Repository
 
from rhodecode.model.notification import NotificationModel
 
from rhodecode.model.scm import ScmModel
 

	
 
log = logging.getLogger(__name__)
 

	
 

	
 
class BasicAuth(AuthBasicAuthenticator):
 

	
 
    def __init__(self, realm, authfunc, auth_http_code=None):
 
        self.realm = realm
 
        self.authfunc = authfunc
 
        self._rc_auth_http_code = auth_http_code
 

	
 
    def build_authentication(self):
 
        head = WWW_AUTHENTICATE.tuples('Basic realm="%s"' % self.realm)
 
        if self._rc_auth_http_code and self._rc_auth_http_code == '403':
 
            # return 403 if alternative http return code is specified in
 
            # RhodeCode config
 
            return HTTPForbidden(headers=head)
 
        return HTTPUnauthorized(headers=head)
 

	
 

	
 
class BaseVCSController(object):
 

	
 
    def __init__(self, application, config):
 
        self.application = application
 
        self.config = config
 
        # base path of repo locations
 
        self.basepath = self.config['base_path']
 
        #authenticate this mercurial request using authfunc
 
        self.authenticate = BasicAuth('', authfunc,
 
                                      config.get('auth_ret_code'))
 
        self.ipaddr = '0.0.0.0'
 

	
 
    def _handle_request(self, environ, start_response):
 
        raise NotImplementedError()
 

	
 
    def _get_by_id(self, repo_name):
 
        """
 
        Get's a special pattern _<ID> from clone url and tries to replace it
 
        with a repository_name for support of _<ID> non changable urls
 

	
 
        :param repo_name:
 
        """
 
        try:
 
            data = repo_name.split('/')
 
            if len(data) >= 2:
 
                by_id = data[1].split('_')
 
                if len(by_id) == 2 and by_id[1].isdigit():
 
                    _repo_name = Repository.get(by_id[1]).repo_name
 
                    data[1] = _repo_name
 
        except:
 
            log.debug('Failed to extract repo_name from id %s' % (
 
                      traceback.format_exc()
 
                      )
 
            )
 

	
 
        return '/'.join(data)
 

	
 
    def _invalidate_cache(self, repo_name):
 
        """
 
        Set's cache for this repository for invalidation on next access
 

	
 
        :param repo_name: full repo name, also a cache key
 
        """
 
        invalidate_cache('get_repo_cached_%s' % repo_name)
 

	
 
    def _check_permission(self, action, user, repo_name):
 
        """
 
        Checks permissions using action (push/pull) user and repository
 
        name
 

	
 
        :param action: push or pull action
 
        :param user: user instance
 
        :param repo_name: repository name
 
        """
 
        if action == 'push':
 
            if not HasPermissionAnyMiddleware('repository.write',
 
                                              'repository.admin')(user,
 
                                                                  repo_name):
 
                return False
 

	
 
        else:
 
            #any other action need at least read permission
 
            if not HasPermissionAnyMiddleware('repository.read',
 
                                              'repository.write',
 
                                              'repository.admin')(user,
 
                                                                  repo_name):
 
                return False
 

	
 
        return True
 

	
 
    def _get_ip_addr(self, environ):
 
        proxy_key = 'HTTP_X_REAL_IP'
 
        proxy_key2 = 'HTTP_X_FORWARDED_FOR'
 
        def_key = 'REMOTE_ADDR'
 

	
 
        return environ.get(proxy_key2,
 
                           environ.get(proxy_key,
 
                                       environ.get(def_key, '0.0.0.0')
 
                            )
 
                        )
 

	
 
    def __call__(self, environ, start_response):
 
        start = time.time()
 
        try:
 
            return self._handle_request(environ, start_response)
 
        finally:
 
            log = logging.getLogger('rhodecode.' + self.__class__.__name__)
 
            log.debug('Request time: %.3fs' % (time.time() - start))
 
            meta.Session.remove()
 

	
 

	
 
class BaseController(WSGIController):
 

	
 
    def __before__(self):
 
        c.rhodecode_version = __version__
 
        c.rhodecode_instanceid = config.get('instance_id')
 
        c.rhodecode_name = config.get('rhodecode_title')
 
        c.use_gravatar = str2bool(config.get('use_gravatar'))
 
        c.ga_code = config.get('rhodecode_ga_code')
 
        c.repo_name = get_repo_slug(request)
 
        c.backends = BACKENDS.keys()
 
        c.unread_notifications = NotificationModel()\
 
                        .get_unread_cnt_for_user(c.rhodecode_user.user_id)
 
        self.cut_off_limit = int(config.get('cut_off_limit'))
 

	
 
        self.sa = meta.Session
 
        self.scm_model = ScmModel(self.sa)
 

	
 
    def __call__(self, environ, start_response):
 
        """Invoke the Controller"""
 
        # WSGIController.__call__ dispatches to the Controller method
 
        # the request is routed to. This routing information is
 
        # available in environ['pylons.routes_dict']
 
        start = time.time()
 
        try:
 
            # make sure that we update permissions each time we call controller
 
            api_key = request.GET.get('api_key')
 
            cookie_store = CookieStoreWrapper(session.get('rhodecode_user'))
 
            user_id = cookie_store.get('user_id', None)
 
            username = get_container_username(environ, config)
 
            auth_user = AuthUser(user_id, api_key, username)
 
            request.user = auth_user
 
            self.rhodecode_user = c.rhodecode_user = auth_user
 
            if not self.rhodecode_user.is_authenticated and \
 
                       self.rhodecode_user.user_id is not None:
 
                self.rhodecode_user.set_authenticated(
 
                    cookie_store.get('is_authenticated')
 
                )
 
            log.info('User: %s accessed %s' % (
 
                auth_user, safe_unicode(environ.get('PATH_INFO')))
 
            )
 
            return WSGIController.__call__(self, environ, start_response)
 
        finally:
 
            log.info('Request to %s time: %.3fs' % (
 
                safe_unicode(environ.get('PATH_INFO')), time.time() - start)
 
            )
 
            meta.Session.remove()
 

	
 

	
 
class BaseRepoController(BaseController):
 
    """
 
    Base class for controllers responsible for loading all needed data for
 
    repository loaded items are
 

	
 
    c.rhodecode_repo: instance of scm repository
 
    c.rhodecode_db_repo: instance of db
 
    c.repository_followers: number of followers
 
    c.repository_forks: number of forks
 
    """
 

	
 
    def __before__(self):
 
        super(BaseRepoController, self).__before__()
 
        if c.repo_name:
 

	
 
            c.rhodecode_db_repo = Repository.get_by_repo_name(c.repo_name)
 
            c.rhodecode_repo = c.rhodecode_db_repo.scm_instance
 

	
 
            if c.rhodecode_repo is None:
 
                log.error('%s this repository is present in database but it '
 
                          'cannot be created as an scm instance', c.repo_name)
 

	
 
                redirect(url('home'))
 

	
 
            c.repository_followers = self.scm_model.get_followers(c.repo_name)
 
            c.repository_forks = self.scm_model.get_forks(c.repo_name)
rhodecode/lib/celerylib/tasks.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
"""
 
    rhodecode.lib.celerylib.tasks
 
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 

	
 
    RhodeCode task modules, containing all task that suppose to be run
 
    by celery daemon
 

	
 
    :created_on: Oct 6, 2010
 
    :author: marcink
 
    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
 
    :license: GPLv3, see COPYING for more details.
 
"""
 
# This program is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# This program is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
from celery.decorators import task
 

	
 
import os
 
import traceback
 
import logging
 
from os.path import join as jn
 

	
 
from time import mktime
 
from operator import itemgetter
 
from string import lower
 

	
 
from pylons import config, url
 
from pylons.i18n.translation import _
 

	
 
from rhodecode.lib.vcs import get_backend
 

	
 
from rhodecode import CELERY_ON
 
from rhodecode.lib.utils2 import safe_str
 
from rhodecode.lib.celerylib import run_task, locked_task, dbsession, \
 
    str2bool, __get_lockkey, LockHeld, DaemonLock, get_session
 
from rhodecode.lib.helpers import person
 
from rhodecode.lib.rcmail.smtp_mailer import SmtpMailer
 
from rhodecode.lib.utils import add_cache, action_logger
 
from rhodecode.lib.compat import json, OrderedDict
 
from rhodecode.lib.hooks import log_create_repository
 

	
 
from rhodecode.model.db import Statistics, Repository, User
 

	
 

	
 
add_cache(config)
 

	
 
__all__ = ['whoosh_index', 'get_commits_stats',
 
           'reset_user_password', 'send_email']
 

	
 

	
 
def get_logger(cls):
 
    if CELERY_ON:
 
        try:
 
            log = cls.get_logger()
 
        except:
 
            log = logging.getLogger(__name__)
 
    else:
 
        log = logging.getLogger(__name__)
 

	
 
    return log
 

	
 

	
 
@task(ignore_result=True)
 
@locked_task
 
@dbsession
 
def whoosh_index(repo_location, full_index):
 
    from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
 
    log = whoosh_index.get_logger(whoosh_index)
 
    DBS = get_session()
 

	
 
    index_location = config['index_dir']
 
    WhooshIndexingDaemon(index_location=index_location,
 
                         repo_location=repo_location, sa=DBS)\
 
                         .run(full_index=full_index)
 

	
 

	
 
@task(ignore_result=True)
 
@dbsession
 
def get_commits_stats(repo_name, ts_min_y, ts_max_y):
 
    log = get_logger(get_commits_stats)
 
    DBS = get_session()
 
    lockkey = __get_lockkey('get_commits_stats', repo_name, ts_min_y,
 
                            ts_max_y)
 
    lockkey_path = config['here']
 

	
 
    log.info('running task with lockkey %s' % lockkey)
 

	
 
    try:
 
        lock = l = DaemonLock(file_=jn(lockkey_path, lockkey))
 

	
 
        # for js data compatibility cleans the key for person from '
 
        akc = lambda k: person(k).replace('"', "")
 

	
 
        co_day_auth_aggr = {}
 
        commits_by_day_aggregate = {}
 
        repo = Repository.get_by_repo_name(repo_name)
 
        if repo is None:
 
            return True
 

	
 
        repo = repo.scm_instance
 
        repo_size = repo.count()
 
        # return if repo have no revisions
 
        if repo_size < 1:
 
            lock.release()
 
            return True
 

	
 
        skip_date_limit = True
 
        parse_limit = int(config['app_conf'].get('commit_parse_limit'))
 
        last_rev = None
 
        last_cs = None
 
        timegetter = itemgetter('time')
 

	
 
        dbrepo = DBS.query(Repository)\
 
            .filter(Repository.repo_name == repo_name).scalar()
 
        cur_stats = DBS.query(Statistics)\
 
            .filter(Statistics.repository == dbrepo).scalar()
 

	
 
        if cur_stats is not None:
 
            last_rev = cur_stats.stat_on_revision
 

	
 
        if last_rev == repo.get_changeset().revision and repo_size > 1:
 
            # pass silently without any work if we're not on first revision or
 
            # current state of parsing revision(from db marker) is the
 
            # last revision
 
            lock.release()
 
            return True
 

	
 
        if cur_stats:
 
            commits_by_day_aggregate = OrderedDict(json.loads(
 
                                        cur_stats.commit_activity_combined))
 
            co_day_auth_aggr = json.loads(cur_stats.commit_activity)
 

	
 
        log.debug('starting parsing %s' % parse_limit)
 
        lmktime = mktime
 

	
 
        last_rev = last_rev + 1 if last_rev >= 0 else 0
 
@@ -279,138 +280,142 @@ def send_password_link(user_email):
 
@dbsession
 
def reset_user_password(user_email):
 
    from rhodecode.lib import auth
 

	
 
    log = get_logger(reset_user_password)
 
    DBS = get_session()
 

	
 
    try:
 
        try:
 
            user = User.get_by_email(user_email)
 
            new_passwd = auth.PasswordGenerator().gen_password(8,
 
                             auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
 
            if user:
 
                user.password = auth.get_crypt_password(new_passwd)
 
                user.api_key = auth.generate_api_key(user.username)
 
                DBS.add(user)
 
                DBS.commit()
 
                log.info('change password for %s' % user_email)
 
            if new_passwd is None:
 
                raise Exception('unable to generate new password')
 
        except:
 
            log.error(traceback.format_exc())
 
            DBS.rollback()
 

	
 
        run_task(send_email, user_email,
 
                 'Your new password',
 
                 'Your new RhodeCode password:%s' % (new_passwd))
 
        log.info('send new password mail to %s' % user_email)
 

	
 
    except:
 
        log.error('Failed to update user password')
 
        log.error(traceback.format_exc())
 

	
 
    return True
 

	
 

	
 
@task(ignore_result=True)
 
@dbsession
 
def send_email(recipients, subject, body, html_body=''):
 
    """
 
    Sends an email with defined parameters from the .ini files.
 

	
 
    :param recipients: list of recipients, it this is empty the defined email
 
        address from field 'email_to' is used instead
 
    :param subject: subject of the mail
 
    :param body: body of the mail
 
    :param html_body: html version of body
 
    """
 
    log = get_logger(send_email)
 
    DBS = get_session()
 

	
 
    email_config = config
 
    subject = "%s %s" % (email_config.get('email_prefix', ''), subject)
 
    if not recipients:
 
        # if recipients are not defined we send to email_config + all admins
 
        admins = [u.email for u in User.query()
 
                  .filter(User.admin == True).all()]
 
        recipients = [email_config.get('email_to')] + admins
 

	
 
    mail_from = email_config.get('app_email_from', 'RhodeCode')
 
    user = email_config.get('smtp_username')
 
    passwd = email_config.get('smtp_password')
 
    mail_server = email_config.get('smtp_server')
 
    mail_port = email_config.get('smtp_port')
 
    tls = str2bool(email_config.get('smtp_use_tls'))
 
    ssl = str2bool(email_config.get('smtp_use_ssl'))
 
    debug = str2bool(config.get('debug'))
 
    smtp_auth = email_config.get('smtp_auth')
 

	
 
    try:
 
        m = SmtpMailer(mail_from, user, passwd, mail_server, smtp_auth,
 
                       mail_port, ssl, tls, debug=debug)
 
        m.send(recipients, subject, body, html_body)
 
    except:
 
        log.error('Mail sending failed')
 
        log.error(traceback.format_exc())
 
        return False
 
    return True
 

	
 

	
 
@task(ignore_result=True)
 
@dbsession
 
def create_repo_fork(form_data, cur_user):
 
    """
 
    Creates a fork of repository using interval VCS methods
 

	
 
    :param form_data:
 
    :param cur_user:
 
    """
 
    from rhodecode.model.repo import RepoModel
 

	
 
    log = get_logger(create_repo_fork)
 
    DBS = get_session()
 

	
 
    base_path = Repository.base_path()
 

	
 
    RepoModel(DBS).create(form_data, cur_user, just_db=True, fork=True)
 
    fork_repo = RepoModel(DBS).create(form_data, cur_user,
 
                                      just_db=True, fork=True)
 

	
 
    alias = form_data['repo_type']
 
    org_repo_name = form_data['org_path']
 
    fork_name = form_data['repo_name_full']
 
    update_after_clone = form_data['update_after_clone']
 
    source_repo_path = os.path.join(base_path, org_repo_name)
 
    destination_fork_path = os.path.join(base_path, fork_name)
 

	
 
    log.info('creating fork of %s as %s', source_repo_path,
 
             destination_fork_path)
 
    backend = get_backend(alias)
 
    backend(safe_str(destination_fork_path), create=True,
 
            src_url=safe_str(source_repo_path),
 
            update_after_clone=update_after_clone)
 
    log_create_repository(fork_repo.get_dict(), created_by=cur_user.username)
 

	
 
    action_logger(cur_user, 'user_forked_repo:%s' % fork_name,
 
                   org_repo_name, '', DBS)
 

	
 
    action_logger(cur_user, 'user_created_fork:%s' % fork_name,
 
                   fork_name, '', DBS)
 
    # finally commit at latest possible stage
 
    DBS.commit()
 

	
 

	
 
def __get_codes_stats(repo_name):
 
    from rhodecode.config.conf import  LANGUAGES_EXTENSIONS_MAP
 
    repo = Repository.get_by_repo_name(repo_name).scm_instance
 

	
 
    tip = repo.get_changeset()
 
    code_stats = {}
 

	
 
    def aggregate(cs):
 
        for f in cs[2]:
 
            ext = lower(f.extension)
 
            if ext in LANGUAGES_EXTENSIONS_MAP.keys() and not f.is_binary:
 
                if ext in code_stats:
 
                    code_stats[ext] += 1
 
                else:
 
                    code_stats[ext] = 1
 

	
 
    map(aggregate, tip.walk('/'))
 

	
 
    return code_stats or {}
rhodecode/lib/compat.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
"""
 
    rhodecode.lib.compat
 
    ~~~~~~~~~~~~~~~~~~~~
 

	
 
    Python backward compatibility functions and common libs
 

	
 

	
 
    :created_on: Oct 7, 2011
 
    :author: marcink
 
    :copyright: (C) 2010-2010 Marcin Kuzminski <marcin@python-works.com>
 
    :license: GPLv3, see COPYING for more details.
 
"""
 
# This program is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# This program is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 

	
 
import os
 
import datetime
 
import functools
 
import decimal
 
from rhodecode import __platform__, PLATFORM_WIN
 

	
 
#==============================================================================
 
# json
 
#==============================================================================
 

	
 

	
 
def _is_aware(value):
 
    """
 
    Determines if a given datetime.time is aware.
 

	
 
    The logic is described in Python's docs:
 
    http://docs.python.org/library/datetime.html#datetime.tzinfo
 
    """
 
    return (value.tzinfo is not None
 
            and value.tzinfo.utcoffset(value) is not None)
 

	
 

	
 
def _obj_dump(obj):
 
    """
 
    Custom function for dumping objects to JSON, if obj has __json__ attribute
 
    or method defined it will be used for serialization
 

	
 
    :param obj:
 
    """
 

	
 
    if isinstance(obj, complex):
 
        return [obj.real, obj.imag]
 
    # See "Date Time String Format" in the ECMA-262 specification.
 
    # some code borrowed from django 1.4
 
    elif isinstance(obj, datetime.datetime):
 
        r = obj.isoformat()
 
        if obj.microsecond:
 
            r = r[:23] + r[26:]
 
        if r.endswith('+00:00'):
 
            r = r[:-6] + 'Z'
 
        return r
 
    elif isinstance(obj, datetime.date):
 
        return obj.isoformat()
 
    elif isinstance(obj, decimal.Decimal):
 
        return str(obj)
 
    elif isinstance(obj, datetime.time):
 
        if _is_aware(obj):
 
            raise ValueError("JSON can't represent timezone-aware times.")
 
        r = obj.isoformat()
 
        if obj.microsecond:
 
            r = r[:12]
 
        return r
 
    elif isinstance(obj, set):
 
        return list(obj)
 
    elif isinstance(obj, OrderedDict):
 
        return obj.as_dict()
 
    elif hasattr(obj, '__json__'):
 
        if callable(obj.__json__):
 
            return obj.__json__()
 
        else:
 
            return obj.__json__
 
    else:
 
        raise NotImplementedError
 

	
 
try:
 
    import json
 

	
 
    # extended JSON encoder for json
 
    class ExtendedEncoder(json.JSONEncoder):
 
        def default(self, obj):
 
            try:
 
                return _obj_dump(obj)
 
            except NotImplementedError:
 
                pass
 
            return json.JSONEncoder.default(self, obj)
 
    # monkey-patch JSON encoder to use extended version
 
    json.dumps = functools.partial(json.dumps, cls=ExtendedEncoder)
 
except ImportError:
 
    import simplejson as json
 

	
 
    def extended_encode(obj):
 
        try:
 
            return _obj_dump(obj)
 
        except NotImplementedError:
 
            pass
 
        raise TypeError("%r is not JSON serializable" % (obj,))
 
    json.dumps = functools.partial(json.dumps, default=extended_encode)
 
from rhodecode.lib.ext_json import json
 

	
 

	
 
#==============================================================================
 
# izip_longest
 
#==============================================================================
 
try:
 
    from itertools import izip_longest
 
except ImportError:
 
    import itertools
 

	
 
    def izip_longest(*args, **kwds):
 
        fillvalue = kwds.get("fillvalue")
 

	
 
        def sentinel(counter=([fillvalue] * (len(args) - 1)).pop):
 
            yield counter()  # yields the fillvalue, or raises IndexError
 

	
 
        fillers = itertools.repeat(fillvalue)
 
        iters = [itertools.chain(it, sentinel(), fillers)
 
                    for it in args]
 
        try:
 
            for tup in itertools.izip(*iters):
 
                yield tup
 
        except IndexError:
 
            pass
 

	
 

	
 
#==============================================================================
 
# OrderedDict
 
#==============================================================================
 

	
 
# Python Software Foundation License
 

	
 
# XXX: it feels like using the class with "is" and "is not" instead of "==" and
 
# "!=" should be faster.
 
class _Nil(object):
 

	
 
    def __repr__(self):
 
        return "nil"
 

	
 
    def __eq__(self, other):
 
        if (isinstance(other, _Nil)):
 
            return True
 
        else:
 
            return NotImplemented
 

	
 
    def __ne__(self, other):
 
        if (isinstance(other, _Nil)):
 
            return False
 
        else:
 
            return NotImplemented
 

	
 
_nil = _Nil()
 

	
 

	
 
class _odict(object):
 
    """Ordered dict data structure, with O(1) complexity for dict operations
 
    that modify one element.
 

	
 
    Overwriting values doesn't change their original sequential order.
 
    """
 

	
 
    def _dict_impl(self):
 
        return None
 

	
 
    def __init__(self, data=(), **kwds):
 
        """This doesn't accept keyword initialization as normal dicts to avoid
 
        a trap - inside a function or method the keyword args are accessible
 
        only as a dict, without a defined order, so their original order is
 
        lost.
 
        """
 
        if kwds:
 
            raise TypeError("__init__() of ordered dict takes no keyword "
 
                            "arguments to avoid an ordering trap.")
 
        self._dict_impl().__init__(self)
 
        # If you give a normal dict, then the order of elements is undefined
 
        if hasattr(data, "iteritems"):
 
            for key, val in data.iteritems():
 
                self[key] = val
 
        else:
 
            for key, val in data:
 
                self[key] = val
 

	
 
    # Double-linked list header
 
    def _get_lh(self):
 
        dict_impl = self._dict_impl()
 
        if not hasattr(self, '_lh'):
 
            dict_impl.__setattr__(self, '_lh', _nil)
 
        return dict_impl.__getattribute__(self, '_lh')
 

	
 
    def _set_lh(self, val):
 
        self._dict_impl().__setattr__(self, '_lh', val)
 

	
 
    lh = property(_get_lh, _set_lh)
 

	
 
    # Double-linked list tail
 
    def _get_lt(self):
rhodecode/lib/dbmigrate/versions/003_version_1_2_0.py
Show inline comments
 
import logging
 
import datetime
 

	
 
from sqlalchemy import *
 
from sqlalchemy.exc import DatabaseError
 
from sqlalchemy.orm import relation, backref, class_mapper
 
from sqlalchemy.orm.session import Session
 

	
 
from rhodecode.lib.dbmigrate.migrate import *
 
from rhodecode.lib.dbmigrate.migrate.changeset import *
 

	
 
from rhodecode.model.meta import Base
 

	
 
log = logging.getLogger(__name__)
 

	
 

	
 
def upgrade(migrate_engine):
 
    """ Upgrade operations go here.
 
    Don't create your own engine; bind migrate_engine to your metadata
 
    """
 

	
 
    #==========================================================================
 
    # Add table `groups``
 
    #==========================================================================
 
    from rhodecode.lib.dbmigrate.schema.db_1_2_0 import Group as Group
 
    Group().__table__.create()
 

	
 
    #==========================================================================
 
    # Add table `group_to_perm`
 
    #==========================================================================
 
    from rhodecode.lib.dbmigrate.schema.db_1_2_0 import UserRepoGroupToPerm
 
    UserRepoGroupToPerm().__table__.create()
 

	
 
    #==========================================================================
 
    # Add table `users_groups`
 
    #==========================================================================
 
    from rhodecode.lib.dbmigrate.schema.db_1_2_0 import UsersGroup
 
    UsersGroup().__table__.create()
 

	
 
    #==========================================================================
 
    # Add table `users_groups_members`
 
    #==========================================================================
 
    from rhodecode.lib.dbmigrate.schema.db_1_2_0 import UsersGroupMember
 
    UsersGroupMember().__table__.create()
 

	
 
    #==========================================================================
 
    # Add table `users_group_repo_to_perm`
 
    #==========================================================================
 
    from rhodecode.lib.dbmigrate.schema.db_1_2_0 import UsersGroupRepoToPerm
 
    UsersGroupRepoToPerm().__table__.create()
 

	
 
    #==========================================================================
 
    # Add table `users_group_to_perm`
 
    #==========================================================================
 
    from rhodecode.lib.dbmigrate.schema.db_1_2_0 import UsersGroupToPerm
 
    UsersGroupToPerm().__table__.create()
 

	
 
    #==========================================================================
 
    # Upgrade of `users` table
 
    #==========================================================================
 
    from rhodecode.lib.dbmigrate.schema.db_1_2_0 import User
 

	
 
    #add column
 
    ldap_dn = Column("ldap_dn", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
 
    ldap_dn.create(User().__table__)
 

	
 
    api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
 
    api_key.create(User().__table__)
 

	
 
    #remove old column
 
    is_ldap = Column("is_ldap", Boolean(), nullable=False, unique=None, default=False)
 
    is_ldap.drop(User().__table__)
 

	
 

	
 
    #==========================================================================
 
    # Upgrade of `repositories` table
 
    #==========================================================================
 
    from rhodecode.lib.dbmigrate.schema.db_1_2_0 import Repository
 

	
 
    #ADD clone_uri column#
 

	
 
    clone_uri = Column("clone_uri", String(length=255, convert_unicode=False,
 
                                           assert_unicode=None),
 
                        nullable=True, unique=False, default=None)
 

	
 
    clone_uri.create(Repository().__table__)
 

	
 
    #ADD downloads column#
 
    enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
 
    enable_downloads.create(Repository().__table__)
 

	
 
    #ADD column created_on
 
    created_on = Column('created_on', DateTime(timezone=False), nullable=True,
 
                        unique=None, default=datetime.datetime.now)
 
    created_on.create(Repository().__table__)
 

	
 
    #ADD group_id column#
 
    group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'),
 
                  nullable=True, unique=False, default=None)
 

	
 
    group_id.create(Repository().__table__)
 

	
 

	
 
    #==========================================================================
 
    # Upgrade of `user_followings` table
 
    #==========================================================================
 

	
 
    from rhodecode.lib.dbmigrate.schema.db_1_2_0 import UserFollowing
 

	
 
    follows_from = Column('follows_from', DateTime(timezone=False),
 
                          nullable=True, unique=None,
 
                          default=datetime.datetime.now)
 
    follows_from.create(UserFollowing().__table__)
 

	
 
    return
 

	
 

	
 
def downgrade(migrate_engine):
 
    meta = MetaData()
 
    meta.bind = migrate_engine
rhodecode/lib/diffs.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
"""
 
    rhodecode.lib.diffs
 
    ~~~~~~~~~~~~~~~~~~~
 

	
 
    Set of diffing helpers, previously part of vcs
 

	
 

	
 
    :created_on: Dec 4, 2011
 
    :author: marcink
 
    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
 
    :original copyright: 2007-2008 by Armin Ronacher
 
    :license: GPLv3, see COPYING for more details.
 
"""
 
# This program is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# This program is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 

	
 
import re
 
import difflib
 
import markupsafe
 
from itertools import tee, imap
 

	
 
from pylons.i18n.translation import _
 

	
 
from rhodecode.lib.vcs.exceptions import VCSError
 
from rhodecode.lib.vcs.nodes import FileNode
 

	
 
from rhodecode.lib.vcs.nodes import FileNode, SubModuleNode
 
from rhodecode.lib.helpers import escape
 
from rhodecode.lib.utils import EmptyChangeset
 

	
 

	
 
def wrap_to_table(str_):
 
    return '''<table class="code-difftable">
 
                <tr class="line no-comment">
 
                <td class="lineno new"></td>
 
                <td class="code no-comment"><pre>%s</pre></td>
 
                </tr>
 
              </table>''' % str_
 

	
 

	
 
def wrapped_diff(filenode_old, filenode_new, cut_off_limit=None,
 
                ignore_whitespace=True, line_context=3,
 
                enable_comments=False):
 
    """
 
    returns a wrapped diff into a table, checks for cut_off_limit and presents
 
    proper message
 
    """
 

	
 
    if filenode_old is None:
 
        filenode_old = FileNode(filenode_new.path, '', EmptyChangeset())
 

	
 
    if filenode_old.is_binary or filenode_new.is_binary:
 
        diff = wrap_to_table(_('binary file'))
 
        stats = (0, 0)
 
        size = 0
 

	
 
    elif cut_off_limit != -1 and (cut_off_limit is None or
 
    (filenode_old.size < cut_off_limit and filenode_new.size < cut_off_limit)):
 

	
 
        f_gitdiff = get_gitdiff(filenode_old, filenode_new,
 
                                ignore_whitespace=ignore_whitespace,
 
                                context=line_context)
 
        diff_processor = DiffProcessor(f_gitdiff, format='gitdiff')
 

	
 
        diff = diff_processor.as_html(enable_comments=enable_comments)
 
        stats = diff_processor.stat()
 
        size = len(diff or '')
 
    else:
 
        diff = wrap_to_table(_('Changeset was to big and was cut off, use '
 
                               'diff menu to display this diff'))
 
        stats = (0, 0)
 
        size = 0
 

	
 
    if not diff:
 
        submodules = filter(lambda o: isinstance(o, SubModuleNode),
 
                            [filenode_new, filenode_old])
 
        if submodules:
 
            diff = wrap_to_table(escape('Submodule %r' % submodules[0]))
 
        else:
 
        diff = wrap_to_table(_('No changes detected'))
 

	
 
    cs1 = filenode_old.changeset.raw_id
 
    cs2 = filenode_new.changeset.raw_id
 

	
 
    return size, cs1, cs2, diff, stats
 

	
 

	
 
def get_gitdiff(filenode_old, filenode_new, ignore_whitespace=True, context=3):
 
    """
 
    Returns git style diff between given ``filenode_old`` and ``filenode_new``.
 

	
 
    :param ignore_whitespace: ignore whitespaces in diff
 
    """
 
    # make sure we pass in default context
 
    context = context or 3
 
    submodules = filter(lambda o: isinstance(o, SubModuleNode),
 
                        [filenode_new, filenode_old])
 
    if submodules:
 
        return ''
 

	
 
    for filenode in (filenode_old, filenode_new):
 
        if not isinstance(filenode, FileNode):
 
            raise VCSError("Given object should be FileNode object, not %s"
 
                % filenode.__class__)
 

	
 
    repo = filenode_new.changeset.repository
 
    old_raw_id = getattr(filenode_old.changeset, 'raw_id', repo.EMPTY_CHANGESET)
 
    new_raw_id = getattr(filenode_new.changeset, 'raw_id', repo.EMPTY_CHANGESET)
 

	
 
    vcs_gitdiff = repo.get_diff(old_raw_id, new_raw_id, filenode_new.path,
 
                                 ignore_whitespace, context)
 

	
 
    return vcs_gitdiff
 

	
 

	
 
class DiffProcessor(object):
 
    """
 
    Give it a unified diff and it returns a list of the files that were
 
    mentioned in the diff together with a dict of meta information that
 
    can be used to render it in a HTML template.
 
    """
 
    _chunk_re = re.compile(r'@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@(.*)')
 

	
 
    def __init__(self, diff, differ='diff', format='udiff'):
 
        """
 
        :param diff:   a text in diff format or generator
 
        :param format: format of diff passed, `udiff` or `gitdiff`
 
        """
 
        if isinstance(diff, basestring):
 
            diff = [diff]
 

	
 
        self.__udiff = diff
 
        self.__format = format
 
        self.adds = 0
 
        self.removes = 0
 

	
 
        if isinstance(self.__udiff, basestring):
 
            self.lines = iter(self.__udiff.splitlines(1))
 

	
 
        elif self.__format == 'gitdiff':
 
            udiff_copy = self.copy_iterator()
 
            self.lines = imap(self.escaper, self._parse_gitdiff(udiff_copy))
 
        else:
 
            udiff_copy = self.copy_iterator()
 
            self.lines = imap(self.escaper, udiff_copy)
 

	
 
        # Select a differ.
 
        if differ == 'difflib':
 
            self.differ = self._highlight_line_difflib
 
        else:
 
            self.differ = self._highlight_line_udiff
 

	
 
    def escaper(self, string):
 
        return markupsafe.escape(string)
 

	
 
    def copy_iterator(self):
 
        """
 
        make a fresh copy of generator, we should not iterate thru
 
        an original as it's needed for repeating operations on
 
        this instance of DiffProcessor
 
        """
 
        self.__udiff, iterator_copy = tee(self.__udiff)
 
        return iterator_copy
 

	
 
    def _extract_rev(self, line1, line2):
 
        """
 
        Extract the filename and revision hint from a line.
 
        """
 

	
 
        try:
 
            if line1.startswith('--- ') and line2.startswith('+++ '):
 
                l1 = line1[4:].split(None, 1)
 
                old_filename = (l1[0].replace('a/', '', 1)
 
                                if len(l1) >= 1 else None)
 
                old_rev = l1[1] if len(l1) == 2 else 'old'
 

	
 
                l2 = line2[4:].split(None, 1)
 
                new_filename = (l2[0].replace('b/', '', 1)
 
                                if len(l1) >= 1 else None)
 
                new_rev = l2[1] if len(l2) == 2 else 'new'
 

	
 
                filename = (old_filename
 
                            if old_filename != '/dev/null' else new_filename)
 

	
 
                return filename, new_rev, old_rev
 
        except (ValueError, IndexError):
 
            pass
 

	
 
        return None, None, None
 

	
 
    def _parse_gitdiff(self, diffiterator):
 
        def line_decoder(l):
 
            if l.startswith('+') and not l.startswith('+++'):
 
                self.adds += 1
 
            elif l.startswith('-') and not l.startswith('---'):
 
                self.removes += 1
 
            return l.decode('utf8', 'replace')
 

	
 
        output = list(diffiterator)
 
        size = len(output)
 

	
 
        if size == 2:
 
            l = []
 
            l.extend([output[0]])
 
            l.extend(output[1].splitlines(1))
 
            return map(line_decoder, l)
 
        elif size == 1:
 
            return  map(line_decoder, output[0].splitlines(1))
rhodecode/lib/ext_json.py
Show inline comments
 
new file 100644
 
import datetime
 
import functools
 
import decimal
 

	
 
__all__ = ['json', 'simplejson', 'stdjson']
 

	
 

	
 
def _is_aware(value):
 
    """
 
    Determines if a given datetime.time is aware.
 

	
 
    The logic is described in Python's docs:
 
    http://docs.python.org/library/datetime.html#datetime.tzinfo
 
    """
 
    return (value.tzinfo is not None
 
            and value.tzinfo.utcoffset(value) is not None)
 

	
 

	
 
def _obj_dump(obj):
 
    """
 
    Custom function for dumping objects to JSON, if obj has __json__ attribute
 
    or method defined it will be used for serialization
 

	
 
    :param obj:
 
    """
 

	
 
    if isinstance(obj, complex):
 
        return [obj.real, obj.imag]
 
    # See "Date Time String Format" in the ECMA-262 specification.
 
    # some code borrowed from django 1.4
 
    elif isinstance(obj, datetime.datetime):
 
        r = obj.isoformat()
 
        if obj.microsecond:
 
            r = r[:23] + r[26:]
 
        if r.endswith('+00:00'):
 
            r = r[:-6] + 'Z'
 
        return r
 
    elif isinstance(obj, datetime.date):
 
        return obj.isoformat()
 
    elif isinstance(obj, decimal.Decimal):
 
        return str(obj)
 
    elif isinstance(obj, datetime.time):
 
        if _is_aware(obj):
 
            raise ValueError("JSON can't represent timezone-aware times.")
 
        r = obj.isoformat()
 
        if obj.microsecond:
 
            r = r[:12]
 
        return r
 
    elif isinstance(obj, set):
 
        return list(obj)
 
    elif hasattr(obj, '__json__'):
 
        if callable(obj.__json__):
 
            return obj.__json__()
 
        else:
 
            return obj.__json__
 
    else:
 
        raise NotImplementedError
 

	
 

	
 
# Import simplejson
 
try:
 
    # import simplejson initially
 
    import simplejson as _sj
 

	
 
    def extended_encode(obj):
 
        try:
 
            return _obj_dump(obj)
 
        except NotImplementedError:
 
            pass
 
        raise TypeError("%r is not JSON serializable" % (obj,))
 
    # we handle decimals our own it makes unified behavior of json vs
 
    # simplejson
 
    _sj.dumps = functools.partial(_sj.dumps, default=extended_encode,
 
                                  use_decimal=False)
 
    _sj.dump = functools.partial(_sj.dump, default=extended_encode,
 
                                 use_decimal=False)
 
    simplejson = _sj
 

	
 
except ImportError:
 
    # no simplejson set it to None
 
    _sj = None
 

	
 

	
 
# simplejson not found try out regular json module
 
import json as _json
 

	
 

	
 
# extended JSON encoder for json
 
class ExtendedEncoder(_json.JSONEncoder):
 
    def default(self, obj):
 
        try:
 
            return _obj_dump(obj)
 
        except NotImplementedError:
 
            pass
 
        return _json.JSONEncoder.default(self, obj)
 
# monkey-patch JSON encoder to use extended version
 
_json.dumps = functools.partial(_json.dumps, cls=ExtendedEncoder)
 
_json.dump = functools.partial(_json.dump, cls=ExtendedEncoder)
 
stdlib = _json
 

	
 
# set all available json modules
 
simplejson = _sj
 
stdjson = _json
 
json = _sj if _sj else _json
rhodecode/lib/helpers.py
Show inline comments
 
@@ -361,192 +361,195 @@ def is_hg(repository):
 
        _type = repository
 
    return _type == 'hg'
 

	
 

	
 
def email_or_none(author):
 
    _email = email(author)
 
    if _email != '':
 
        return _email
 

	
 
    # See if it contains a username we can get an email from
 
    user = User.get_by_username(author_name(author), case_insensitive=True,
 
                                cache=True)
 
    if user is not None:
 
        return user.email
 

	
 
    # No valid email, not a valid user in the system, none!
 
    return None
 

	
 

	
 
def person(author):
 
    # attr to return from fetched user
 
    person_getter = lambda usr: usr.username
 

	
 
    # Valid email in the attribute passed, see if they're in the system
 
    _email = email(author)
 
    if _email != '':
 
        user = User.get_by_email(_email, case_insensitive=True, cache=True)
 
        if user is not None:
 
            return person_getter(user)
 
        return _email
 

	
 
    # Maybe it's a username?
 
    _author = author_name(author)
 
    user = User.get_by_username(_author, case_insensitive=True,
 
                                cache=True)
 
    if user is not None:
 
        return person_getter(user)
 

	
 
    # Still nothing?  Just pass back the author name then
 
    return _author
 

	
 

	
 
def bool2icon(value):
 
    """Returns True/False values represented as small html image of true/false
 
    icons
 

	
 
    :param value: bool value
 
    """
 

	
 
    if value is True:
 
        return HTML.tag('img', src=url("/images/icons/accept.png"),
 
                        alt=_('True'))
 

	
 
    if value is False:
 
        return HTML.tag('img', src=url("/images/icons/cancel.png"),
 
                        alt=_('False'))
 

	
 
    return value
 

	
 

	
 
def action_parser(user_log, feed=False):
 
    """
 
    This helper will action_map the specified string action into translated
 
    fancy names with icons and links
 

	
 
    :param user_log: user log instance
 
    :param feed: use output for feeds (no html and fancy icons)
 
    """
 

	
 
    action = user_log.action
 
    action_params = ' '
 

	
 
    x = action.split(':')
 

	
 
    if len(x) > 1:
 
        action, action_params = x
 

	
 
    def get_cs_links():
 
        revs_limit = 3  # display this amount always
 
        revs_top_limit = 50  # show upto this amount of changesets hidden
 
        revs_ids = action_params.split(',')
 
        deleted = user_log.repository is None
 
        if deleted:
 
            return ','.join(revs_ids)
 

	
 
        repo_name = user_log.repository.repo_name
 

	
 
        repo = user_log.repository.scm_instance
 

	
 
        message = lambda rev: rev.message
 
        lnk = lambda rev, repo_name: (
 
            link_to('r%s:%s' % (rev.revision, rev.short_id),
 
                    url('changeset_home', repo_name=repo_name,
 
                        revision=rev.raw_id),
 
                    title=tooltip(message(rev)), class_='tooltip')
 
        )
 

	
 
        revs = []
 
        if len(filter(lambda v: v != '', revs_ids)) > 0:
 
        # get only max revs_top_limit of changeset for performance/ui reasons
 
        revs = [
 
            x for x in repo.get_changesets(revs_ids[0],
 
                                           revs_ids[:revs_top_limit][-1])
 
        ]
 

	
 
        cs_links = []
 
        cs_links.append(" " + ', '.join(
 
            [lnk(rev, repo_name) for rev in revs[:revs_limit]]
 
            )
 
        )
 

	
 
        compare_view = (
 
            ' <div class="compare_view tooltip" title="%s">'
 
            '<a href="%s">%s</a> </div>' % (
 
                _('Show all combined changesets %s->%s') % (
 
                    revs_ids[0], revs_ids[-1]
 
                ),
 
                url('changeset_home', repo_name=repo_name,
 
                    revision='%s...%s' % (revs_ids[0], revs_ids[-1])
 
                ),
 
                _('compare view')
 
            )
 
        )
 

	
 
        # if we have exactly one more than normally displayed
 
        # just display it, takes less space than displaying
 
        # "and 1 more revisions"
 
        if len(revs_ids) == revs_limit + 1:
 
            rev = revs[revs_limit]
 
            cs_links.append(", " + lnk(rev, repo_name))
 

	
 
        # hidden-by-default ones
 
        if len(revs_ids) > revs_limit + 1:
 
            uniq_id = revs_ids[0]
 
            html_tmpl = (
 
                '<span> %s <a class="show_more" id="_%s" '
 
                'href="#more">%s</a> %s</span>'
 
            )
 
            if not feed:
 
                cs_links.append(html_tmpl % (
 
                      _('and'),
 
                      uniq_id, _('%s more') % (len(revs_ids) - revs_limit),
 
                      _('revisions')
 
                    )
 
                )
 

	
 
            if not feed:
 
                html_tmpl = '<span id="%s" style="display:none">, %s </span>'
 
            else:
 
                html_tmpl = '<span id="%s"> %s </span>'
 

	
 
            morelinks = ', '.join(
 
              [lnk(rev, repo_name) for rev in revs[revs_limit:]]
 
            )
 

	
 
            if len(revs_ids) > revs_top_limit:
 
                morelinks += ', ...'
 

	
 
            cs_links.append(html_tmpl % (uniq_id, morelinks))
 
        if len(revs) > 1:
 
            cs_links.append(compare_view)
 
        return ''.join(cs_links)
 

	
 
    def get_fork_name():
 
        repo_name = action_params
 
        return _('fork name ') + str(link_to(action_params, url('summary_home',
 
                                          repo_name=repo_name,)))
 

	
 
    action_map = {'user_deleted_repo': (_('[deleted] repository'), None),
 
           'user_created_repo': (_('[created] repository'), None),
 
           'user_created_fork': (_('[created] repository as fork'), None),
 
           'user_forked_repo': (_('[forked] repository'), get_fork_name),
 
           'user_updated_repo': (_('[updated] repository'), None),
 
           'admin_deleted_repo': (_('[delete] repository'), None),
 
           'admin_created_repo': (_('[created] repository'), None),
 
           'admin_forked_repo': (_('[forked] repository'), None),
 
           'admin_updated_repo': (_('[updated] repository'), None),
 
           'push': (_('[pushed] into'), get_cs_links),
 
           'push_local': (_('[committed via RhodeCode] into'), get_cs_links),
 
           'push_remote': (_('[pulled from remote] into'), get_cs_links),
 
           'pull': (_('[pulled] from'), None),
 
           'started_following_repo': (_('[started following] repository'), None),
 
           'stopped_following_repo': (_('[stopped following] repository'), None),
 
            }
 

	
 
    action_str = action_map.get(action, action)
 
    if feed:
 
        action = action_str[0].replace('[', '').replace(']', '')
 
    else:
 
        action = action_str[0]\
 
            .replace('[', '<span class="journal_highlight">')\
 
            .replace(']', '</span>')
 

	
 
    action_params_func = lambda: ""
 

	
rhodecode/lib/hooks.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
"""
 
    rhodecode.lib.hooks
 
    ~~~~~~~~~~~~~~~~~~~
 

	
 
    Hooks runned by rhodecode
 

	
 
    :created_on: Aug 6, 2010
 
    :author: marcink
 
    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
 
    :license: GPLv3, see COPYING for more details.
 
"""
 
# This program is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# This program is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
import os
 
import sys
 

	
 
from mercurial.scmutil import revrange
 
from mercurial.node import nullrev
 
from rhodecode import EXTENSIONS
 
from rhodecode.lib import helpers as h
 
from rhodecode.lib.utils import action_logger
 
from inspect import isfunction
 

	
 

	
 
def _get_scm_size(alias, root_path):
 

	
 
    if not alias.startswith('.'):
 
        alias += '.'
 

	
 
    size_scm, size_root = 0, 0
 
    for path, dirs, files in os.walk(root_path):
 
        if path.find(alias) != -1:
 
            for f in files:
 
                try:
 
                    size_scm += os.path.getsize(os.path.join(path, f))
 
                except OSError:
 
                    pass
 
        else:
 
            for f in files:
 
                try:
 
                    size_root += os.path.getsize(os.path.join(path, f))
 
                except OSError:
 
                    pass
 

	
 
    size_scm_f = h.format_byte_size(size_scm)
 
    size_root_f = h.format_byte_size(size_root)
 
    size_total_f = h.format_byte_size(size_root + size_scm)
 

	
 
    return size_scm_f, size_root_f, size_total_f
 

	
 

	
 
def repo_size(ui, repo, hooktype=None, **kwargs):
 
    """
 
    Presents size of repository after push
 

	
 
    :param ui:
 
    :param repo:
 
    :param hooktype:
 
    """
 

	
 
    size_hg, size_root = 0, 0
 
    for path, dirs, files in os.walk(repo.root):
 
        if path.find('.hg') != -1:
 
            for f in files:
 
                try:
 
                    size_hg += os.path.getsize(os.path.join(path, f))
 
                except OSError:
 
                    pass
 
        else:
 
            for f in files:
 
                try:
 
                    size_root += os.path.getsize(os.path.join(path, f))
 
                except OSError:
 
                    pass
 

	
 
    size_hg_f = h.format_byte_size(size_hg)
 
    size_root_f = h.format_byte_size(size_root)
 
    size_total_f = h.format_byte_size(size_root + size_hg)
 
    size_hg_f, size_root_f, size_total_f = _get_scm_size('.hg', repo.root)
 

	
 
    last_cs = repo[len(repo) - 1]
 

	
 
    msg = ('Repository size .hg:%s repo:%s total:%s\n'
 
           'Last revision is now r%s:%s\n') % (
 
        size_hg_f, size_root_f, size_total_f, last_cs.rev(), last_cs.hex()[:12]
 
    )
 

	
 
    sys.stdout.write(msg)
 

	
 

	
 
def log_pull_action(ui, repo, **kwargs):
 
    """
 
    Logs user last pull action
 

	
 
    :param ui:
 
    :param repo:
 
    """
 

	
 
    extras = dict(repo.ui.configitems('rhodecode_extras'))
 
    username = extras['username']
 
    repository = extras['repository']
 
    scm = extras['scm']
 
    action = 'pull'
 

	
 
    action_logger(username, action, repository, extras['ip'], commit=True)
 
    # extension hook call
 
    callback = getattr(EXTENSIONS, 'PULL_HOOK', None)
 

	
 
    if isfunction(callback):
 
        kw = {}
 
        kw.update(extras)
 
        callback(**kw)
 
    return 0
 

	
 

	
 
def log_push_action(ui, repo, **kwargs):
 
    """
 
    Maps user last push action to new changeset id, from mercurial
 

	
 
    :param ui:
 
    :param repo:
 
    :param repo: repo object containing the `ui` object
 
    """
 

	
 
    extras = dict(repo.ui.configitems('rhodecode_extras'))
 
    username = extras['username']
 
    repository = extras['repository']
 
    action = extras['action'] + ':%s'
 
    scm = extras['scm']
 

	
 
    if scm == 'hg':
 
    node = kwargs['node']
 

	
 
    def get_revs(repo, rev_opt):
 
        if rev_opt:
 
            revs = revrange(repo, rev_opt)
 

	
 
            if len(revs) == 0:
 
                return (nullrev, nullrev)
 
            return (max(revs), min(revs))
 
        else:
 
            return (len(repo) - 1, 0)
 

	
 
    stop, start = get_revs(repo, [node + ':'])
 

	
 
    revs = (str(repo[r]) for r in xrange(start, stop + 1))
 
    elif scm == 'git':
 
        revs = []
 

	
 
    action = action % ','.join(revs)
 

	
 
    action_logger(username, action, repository, extras['ip'], commit=True)
 

	
 
    # extension hook call
 
    callback = getattr(EXTENSIONS, 'PUSH_HOOK', None)
 
    if isfunction(callback):
 
        kw = {'pushed_revs': revs}
 
        kw.update(extras)
 
        callback(**kw)
 
    return 0
 

	
 

	
 
def log_create_repository(repository_dict, created_by, **kwargs):
 
    """
 
    Post create repository Hook. This is a dummy function for admins to re-use
 
    if needed. It's taken from rhodecode-extensions module and executed
 
    if present
 

	
 
    :param repository: dict dump of repository object
 
    :param created_by: username who created repository
 
    :param created_date: date of creation
 

	
 
    available keys of repository_dict:
 

	
 
     'repo_type',
 
     'description',
 
     'private',
 
     'created_on',
 
     'enable_downloads',
 
     'repo_id',
 
     'user_id',
 
     'enable_statistics',
 
     'clone_uri',
 
     'fork_id',
 
     'group_id',
 
     'repo_name'
 

	
 
    """
 

	
 
    callback = getattr(EXTENSIONS, 'CREATE_REPO_HOOK', None)
 
    if isfunction(callback):
 
        kw = {}
 
        kw.update(repository_dict)
 
        kw.update({'created_by': created_by})
 
        kw.update(kwargs)
 
        return callback(**kw)
 

	
 
    return 0
rhodecode/lib/markup_renderer.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
"""
 
    rhodecode.lib.markup_renderer
 
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 

	
 

	
 
    Renderer for markup languages with ability to parse using rst or markdown
 

	
 
    :created_on: Oct 27, 2011
 
    :author: marcink
 
    :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
 
    :license: GPLv3, see COPYING for more details.
 
"""
 
# This program is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# This program is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 

	
 
import re
 
import logging
 

	
 
from rhodecode.lib.utils2 import safe_unicode
 
from rhodecode.lib.utils2 import safe_unicode, MENTIONS_REGEX
 

	
 
log = logging.getLogger(__name__)
 

	
 

	
 
class MarkupRenderer(object):
 
    RESTRUCTUREDTEXT_DISALLOWED_DIRECTIVES = ['include', 'meta', 'raw']
 

	
 
    MARKDOWN_PAT = re.compile(r'md|mkdn?|mdown|markdown', re.IGNORECASE)
 
    RST_PAT = re.compile(r're?st', re.IGNORECASE)
 
    PLAIN_PAT = re.compile(r'readme', re.IGNORECASE)
 

	
 
    def __detect_renderer(self, source, filename=None):
 
        """
 
        runs detection of what renderer should be used for generating html
 
        from a markup language
 

	
 
        filename can be also explicitly a renderer name
 

	
 
        :param source:
 
        :param filename:
 
        """
 

	
 
        if MarkupRenderer.MARKDOWN_PAT.findall(filename):
 
            detected_renderer = 'markdown'
 
        elif MarkupRenderer.RST_PAT.findall(filename):
 
            detected_renderer = 'rst'
 
        elif MarkupRenderer.PLAIN_PAT.findall(filename):
 
            detected_renderer = 'rst'
 
        else:
 
            detected_renderer = 'plain'
 

	
 
        return getattr(MarkupRenderer, detected_renderer)
 

	
 
    def render(self, source, filename=None):
 
        """
 
        Renders a given filename using detected renderer
 
        it detects renderers based on file extension or mimetype.
 
        At last it will just do a simple html replacing new lines with <br/>
 

	
 
        :param file_name:
 
        :param source:
 
        """
 

	
 
        renderer = self.__detect_renderer(source, filename)
 
        readme_data = renderer(source)
 
        return readme_data
 

	
 
    @classmethod
 
    def plain(cls, source):
 
        source = safe_unicode(source)
 

	
 
        def urlify_text(text):
 
            url_pat = re.compile(r'(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]'
 
                                 '|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)')
 

	
 
            def url_func(match_obj):
 
                url_full = match_obj.groups()[0]
 
                return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
 

	
 
            return url_pat.sub(url_func, text)
 

	
 
        source = urlify_text(source)
 
        return '<br />' + source.replace("\n", '<br />')
 

	
 
    @classmethod
 
    def markdown(cls, source):
 
        source = safe_unicode(source)
 
        try:
 
            import markdown as __markdown
 
            return __markdown.markdown(source, ['codehilite'])
 
        except ImportError:
 
            log.warning('Install markdown to use this function')
 
            return cls.plain(source)
 

	
 
    @classmethod
 
    def rst(cls, source):
 
        source = safe_unicode(source)
 
        try:
 
            from docutils.core import publish_parts
 
            from docutils.parsers.rst import directives
 
            docutils_settings = dict([(alias, None) for alias in
 
                                cls.RESTRUCTUREDTEXT_DISALLOWED_DIRECTIVES])
 

	
 
            docutils_settings.update({'input_encoding': 'unicode',
 
                                      'report_level': 4})
 

	
 
            for k, v in docutils_settings.iteritems():
 
                directives.register_directive(k, v)
 

	
 
            parts = publish_parts(source=source,
 
                                  writer_name="html4css1",
 
                                  settings_overrides=docutils_settings)
 

	
 
            return parts['html_title'] + parts["fragment"]
 
        except ImportError:
 
            log.warning('Install docutils to use this function')
 
            return cls.plain(source)
 

	
 
    @classmethod
 
    def rst_with_mentions(cls, source):
 
        mention_pat = re.compile(r'(?:^@|\s@)(\w+)')
 
        mention_pat = re.compile(MENTIONS_REGEX)
 

	
 
        def wrapp(match_obj):
 
            uname = match_obj.groups()[0]
 
            return ' **@%(uname)s** ' % {'uname':uname}
 
        mention_hl = mention_pat.sub(wrapp, source).strip()
 
        return cls.rst(mention_hl)
rhodecode/lib/middleware/simplegit.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
"""
 
    rhodecode.lib.middleware.simplegit
 
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 

	
 
    SimpleGit middleware for handling git protocol request (push/clone etc.)
 
    It's implemented with basic auth function
 

	
 
    :created_on: Apr 28, 2010
 
    :author: marcink
 
    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
 
    :license: GPLv3, see COPYING for more details.
 
"""
 
# This program is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# This program is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 

	
 
import os
 
import re
 
import logging
 
import traceback
 

	
 
from dulwich import server as dulserver
 

	
 

	
 
class SimpleGitUploadPackHandler(dulserver.UploadPackHandler):
 

	
 
    def handle(self):
 
        write = lambda x: self.proto.write_sideband(1, x)
 

	
 
        graph_walker = dulserver.ProtocolGraphWalker(self,
 
                                                     self.repo.object_store,
 
                                                     self.repo.get_peeled)
 
        objects_iter = self.repo.fetch_objects(
 
          graph_walker.determine_wants, graph_walker, self.progress,
 
          get_tagged=self.get_tagged)
 

	
 
        # Do they want any objects?
 
        if objects_iter is None or len(objects_iter) == 0:
 
        # Did the process short-circuit (e.g. in a stateless RPC call)? Note
 
        # that the client still expects a 0-object pack in most cases.
 
        if objects_iter is None:
 
            return
 

	
 
        self.progress("counting objects: %d, done.\n" % len(objects_iter))
 
        dulserver.write_pack_objects(dulserver.ProtocolFile(None, write),
 
                                  objects_iter, len(objects_iter))
 
                                     objects_iter)
 
        messages = []
 
        messages.append('thank you for using rhodecode')
 

	
 
        for msg in messages:
 
            self.progress(msg + "\n")
 
        # we are done
 
        self.proto.write("0000")
 

	
 

	
 
dulserver.DEFAULT_HANDLERS = {
 
  'git-upload-pack': SimpleGitUploadPackHandler,
 
  'git-receive-pack': dulserver.ReceivePackHandler,
 
}
 

	
 
from dulwich.repo import Repo
 
from dulwich.web import make_wsgi_chain
 

	
 
from paste.httpheaders import REMOTE_USER, AUTH_TYPE
 

	
 
from rhodecode.lib.utils2 import safe_str
 
from rhodecode.lib.base import BaseVCSController
 
from rhodecode.lib.auth import get_container_username
 
from rhodecode.lib.utils import is_valid_repo
 
from rhodecode.lib.utils import is_valid_repo, make_ui
 
from rhodecode.model.db import User
 

	
 
from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
 

	
 
log = logging.getLogger(__name__)
 

	
 

	
 
GIT_PROTO_PAT = re.compile(r'^/(.+)/(info/refs|git-upload-pack|git-receive-pack)')
 

	
 

	
 
def is_git(environ):
 
    path_info = environ['PATH_INFO']
 
    isgit_path = GIT_PROTO_PAT.match(path_info)
 
    log.debug('pathinfo: %s detected as GIT %s' % (
 
        path_info, isgit_path != None)
 
    )
 
    return isgit_path
 

	
 

	
 
class SimpleGit(BaseVCSController):
 

	
 
    def _handle_request(self, environ, start_response):
 

	
 
        if not is_git(environ):
 
            return self.application(environ, start_response)
 

	
 
        proxy_key = 'HTTP_X_REAL_IP'
 
        def_key = 'REMOTE_ADDR'
 
        ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0'))
 
        ipaddr = self._get_ip_addr(environ)
 
        username = None
 
        self._git_first_op = False
 
        # skip passing error to error controller
 
        environ['pylons.status_code_redirect'] = True
 

	
 
        #======================================================================
 
        # EXTRACT REPOSITORY NAME FROM ENV
 
        #======================================================================
 
        try:
 
            repo_name = self.__get_repository(environ)
 
            log.debug('Extracted repo name is %s' % repo_name)
 
        except:
 
            return HTTPInternalServerError()(environ, start_response)
 

	
 
        # quick check if that dir exists...
 
        if is_valid_repo(repo_name, self.basepath) is False:
 
            return HTTPNotFound()(environ, start_response)
 

	
 
        #======================================================================
 
        # GET ACTION PULL or PUSH
 
        #======================================================================
 
        action = self.__get_action(environ)
 

	
 
        #======================================================================
 
        # CHECK ANONYMOUS PERMISSION
 
        #======================================================================
 
        if action in ['pull', 'push']:
 
            anonymous_user = self.__get_user('default')
 
            username = anonymous_user.username
 
            anonymous_perm = self._check_permission(action, anonymous_user,
 
                                                    repo_name)
 

	
 
            if anonymous_perm is not True or anonymous_user.active is False:
 
                if anonymous_perm is not True:
 
                    log.debug('Not enough credentials to access this '
 
                              'repository as anonymous user')
 
                if anonymous_user.active is False:
 
                    log.debug('Anonymous access is disabled, running '
 
                              'authentication')
 
                #==============================================================
 
                # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
 
                # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
 
                #==============================================================
 

	
 
                # Attempting to retrieve username from the container
 
                username = get_container_username(environ, self.config)
 

	
 
                # If not authenticated by the container, running basic auth
 
                if not username:
 
                    self.authenticate.realm = \
 
                        safe_str(self.config['rhodecode_realm'])
 
                    result = self.authenticate(environ)
 
                    if isinstance(result, str):
 
                        AUTH_TYPE.update(environ, 'basic')
 
                        REMOTE_USER.update(environ, result)
 
                        username = result
 
                    else:
 
                        return result.wsgi_application(environ, start_response)
 

	
 
                #==============================================================
 
                # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
 
                #==============================================================
 
                if action in ['pull', 'push']:
 
                    try:
 
                        user = self.__get_user(username)
 
                        if user is None or not user.active:
 
                            return HTTPForbidden()(environ, start_response)
 
                        username = user.username
 
                    except:
 
                        log.error(traceback.format_exc())
 
                        return HTTPInternalServerError()(environ,
 
                                                         start_response)
 

	
 
                    #check permissions for this repository
 
                    perm = self._check_permission(action, user, repo_name)
 
                    if perm is not True:
 
                        return HTTPForbidden()(environ, start_response)
 
        extras = {
 
            'ip': ipaddr,
 
            'username': username,
 
            'action': action,
 
            'repository': repo_name,
 
            'scm': 'git',
 
        }
 

	
 
        #===================================================================
 
        # GIT REQUEST HANDLING
 
        #===================================================================
 
        repo_path = os.path.join(safe_str(self.basepath), safe_str(repo_name))
 
        log.debug('Repository path is %s' % repo_path)
 

	
 
        baseui = make_ui('db')
 
        self.__inject_extras(repo_path, baseui, extras)
 

	
 

	
 
        try:
 
            #invalidate cache on push
 
            if action == 'push':
 
                self._invalidate_cache(repo_name)
 
            self._handle_githooks(repo_name, action, baseui, environ)
 

	
 
            log.info('%s action on GIT repo "%s"' % (action, repo_name))
 
            app = self.__make_app(repo_name, repo_path)
 
            return app(environ, start_response)
 
        except Exception:
 
            log.error(traceback.format_exc())
 
            return HTTPInternalServerError()(environ, start_response)
 

	
 
    def __make_app(self, repo_name, repo_path):
 
        """
 
        Make an wsgi application using dulserver
 

	
 
        :param repo_name: name of the repository
 
        :param repo_path: full path to the repository
 
        """
 
        _d = {'/' + repo_name: Repo(repo_path)}
 
        backend = dulserver.DictBackend(_d)
 
        gitserve = make_wsgi_chain(backend)
 

	
 
        return gitserve
 

	
 
    def __get_repository(self, environ):
 
        """
 
        Get's repository name out of PATH_INFO header
 

	
 
        :param environ: environ where PATH_INFO is stored
 
        """
 
        try:
 
            environ['PATH_INFO'] = self._get_by_id(environ['PATH_INFO'])
 
            repo_name = GIT_PROTO_PAT.match(environ['PATH_INFO']).group(1)
 
        except:
 
            log.error(traceback.format_exc())
 
            raise
 

	
 
        return repo_name
 

	
 
    def __get_user(self, username):
 
        return User.get_by_username(username)
 

	
 
    def __get_action(self, environ):
 
        """
 
        Maps git request commands into a pull or push command.
 

	
 
        :param environ:
 
        """
 
        service = environ['QUERY_STRING'].split('=')
 

	
 
        if len(service) > 1:
 
            service_cmd = service[1]
 
            mapping = {
 
                'git-receive-pack': 'push',
 
                'git-upload-pack': 'pull',
 
            }
 
            op = mapping[service_cmd]
 
            self._git_stored_op = op
 
            return op
 
        else:
 
            # try to fallback to stored variable as we don't know if the last
 
            # operation is pull/push
 
            op = getattr(self, '_git_stored_op', 'pull')
 
        return op
 

	
 
    def _handle_githooks(self, repo_name, action, baseui, environ):
 
        from rhodecode.lib.hooks import log_pull_action, log_push_action
 
        service = environ['QUERY_STRING'].split('=')
 
        if len(service) < 2:
 
            return
 

	
 
        from rhodecode.model.db import Repository
 
        _repo = Repository.get_by_repo_name(repo_name)
 
        _repo = _repo.scm_instance
 
        _repo._repo.ui = baseui
 

	
 
        push_hook = 'pretxnchangegroup.push_logger'
 
        pull_hook = 'preoutgoing.pull_logger'
 
        _hooks = dict(baseui.configitems('hooks')) or {}
 
        if action == 'push' and _hooks.get(push_hook):
 
            log_push_action(ui=baseui, repo=_repo._repo)
 
        elif action == 'pull' and _hooks.get(pull_hook):
 
            log_pull_action(ui=baseui, repo=_repo._repo)
 

	
 
    def __inject_extras(self, repo_path, baseui, extras={}):
 
        """
 
        Injects some extra params into baseui instance
 

	
 
        :param baseui: baseui instance
 
        :param extras: dict with extra params to put into baseui
 
        """
 

	
 
        # make our hgweb quiet so it doesn't print output
 
        baseui.setconfig('ui', 'quiet', 'true')
 

	
 
        #inject some additional parameters that will be available in ui
 
        #for hooks
 
        for k, v in extras.items():
 
            baseui.setconfig('rhodecode_extras', k, v)
rhodecode/lib/middleware/simplehg.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
"""
 
    rhodecode.lib.middleware.simplehg
 
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 

	
 
    SimpleHG middleware for handling mercurial protocol request
 
    (push/clone etc.). It's implemented with basic auth function
 

	
 
    :created_on: Apr 28, 2010
 
    :author: marcink
 
    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
 
    :license: GPLv3, see COPYING for more details.
 
"""
 
# This program is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# This program is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 

	
 
import os
 
import logging
 
import traceback
 
import urllib
 

	
 
from mercurial.error import RepoError
 
from mercurial.hgweb import hgweb_mod
 

	
 
from paste.httpheaders import REMOTE_USER, AUTH_TYPE
 

	
 
from rhodecode.lib.utils2 import safe_str
 
from rhodecode.lib.base import BaseVCSController
 
from rhodecode.lib.auth import get_container_username
 
from rhodecode.lib.utils import make_ui, is_valid_repo, ui_sections
 
from rhodecode.model.db import User
 

	
 
from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
 

	
 
log = logging.getLogger(__name__)
 

	
 

	
 
def is_mercurial(environ):
 
    """
 
    Returns True if request's target is mercurial server - header
 
    ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
 
    """
 
    http_accept = environ.get('HTTP_ACCEPT')
 
    path_info = environ['PATH_INFO']
 
    if http_accept and http_accept.startswith('application/mercurial'):
 
        ishg_path = True
 
    else:
 
        ishg_path = False
 

	
 
    log.debug('pathinfo: %s detected as HG %s' % (
 
        path_info, ishg_path)
 
    )
 
    return ishg_path
 

	
 

	
 
class SimpleHg(BaseVCSController):
 

	
 
    def _handle_request(self, environ, start_response):
 
        if not is_mercurial(environ):
 
            return self.application(environ, start_response)
 

	
 
        proxy_key = 'HTTP_X_REAL_IP'
 
        def_key = 'REMOTE_ADDR'
 
        ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0'))
 
        ipaddr = self._get_ip_addr(environ)
 

	
 
        # skip passing error to error controller
 
        environ['pylons.status_code_redirect'] = True
 

	
 
        #======================================================================
 
        # EXTRACT REPOSITORY NAME FROM ENV
 
        #======================================================================
 
        try:
 
            repo_name = environ['REPO_NAME'] = self.__get_repository(environ)
 
            log.debug('Extracted repo name is %s' % repo_name)
 
        except:
 
            return HTTPInternalServerError()(environ, start_response)
 

	
 
        # quick check if that dir exists...
 
        if is_valid_repo(repo_name, self.basepath) is False:
 
            return HTTPNotFound()(environ, start_response)
 

	
 
        #======================================================================
 
        # GET ACTION PULL or PUSH
 
        #======================================================================
 
        action = self.__get_action(environ)
 

	
 
        #======================================================================
 
        # CHECK ANONYMOUS PERMISSION
 
        #======================================================================
 
        if action in ['pull', 'push']:
 
            anonymous_user = self.__get_user('default')
 
            username = anonymous_user.username
 
            anonymous_perm = self._check_permission(action, anonymous_user,
 
                                                    repo_name)
 

	
 
            if anonymous_perm is not True or anonymous_user.active is False:
 
                if anonymous_perm is not True:
 
                    log.debug('Not enough credentials to access this '
 
                              'repository as anonymous user')
 
                if anonymous_user.active is False:
 
                    log.debug('Anonymous access is disabled, running '
 
                              'authentication')
 
                #==============================================================
 
                # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
 
                # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
 
                #==============================================================
 

	
 
                # Attempting to retrieve username from the container
 
                username = get_container_username(environ, self.config)
 

	
 
                # If not authenticated by the container, running basic auth
 
                if not username:
 
                    self.authenticate.realm = \
 
                        safe_str(self.config['rhodecode_realm'])
 
                    result = self.authenticate(environ)
 
                    if isinstance(result, str):
 
                        AUTH_TYPE.update(environ, 'basic')
 
                        REMOTE_USER.update(environ, result)
 
                        username = result
 
                    else:
 
                        return result.wsgi_application(environ, start_response)
 

	
 
                #==============================================================
 
                # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME
 
                #==============================================================
 
                if action in ['pull', 'push']:
 
                    try:
 
                        user = self.__get_user(username)
 
                        if user is None or not user.active:
 
                            return HTTPForbidden()(environ, start_response)
 
                        username = user.username
 
                    except:
 
                        log.error(traceback.format_exc())
 
                        return HTTPInternalServerError()(environ,
 
                                                         start_response)
 

	
 
                    #check permissions for this repository
 
                    perm = self._check_permission(action, user, repo_name)
 
                    if perm is not True:
 
                        return HTTPForbidden()(environ, start_response)
 

	
 
        # extras are injected into mercurial UI object and later available
 
        # in hg hooks executed by rhodecode
 
        extras = {
 
            'ip': ipaddr,
 
            'username': username,
 
            'action': action,
 
            'repository': repo_name
 
            'repository': repo_name,
 
            'scm': 'hg',
 
        }
 

	
 
        #======================================================================
 
        # MERCURIAL REQUEST HANDLING
 
        #======================================================================
 
        repo_path = os.path.join(safe_str(self.basepath), safe_str(repo_name))
 
        log.debug('Repository path is %s' % repo_path)
 

	
 
        baseui = make_ui('db')
 
        self.__inject_extras(repo_path, baseui, extras)
 

	
 
        try:
 
            # invalidate cache on push
 
            if action == 'push':
 
                self._invalidate_cache(repo_name)
 
            log.info('%s action on HG repo "%s"' % (action, repo_name))
 
            app = self.__make_app(repo_path, baseui, extras)
 
            return app(environ, start_response)
 
        except RepoError, e:
 
            if str(e).find('not found') != -1:
 
                return HTTPNotFound()(environ, start_response)
 
        except Exception:
 
            log.error(traceback.format_exc())
 
            return HTTPInternalServerError()(environ, start_response)
 

	
 
    def __make_app(self, repo_name, baseui, extras):
 
        """
 
        Make an wsgi application using hgweb, and inject generated baseui
 
        instance, additionally inject some extras into ui object
 
        """
 
        return hgweb_mod.hgweb(repo_name, name=repo_name, baseui=baseui)
 

	
 
    def __get_repository(self, environ):
 
        """
 
        Get's repository name out of PATH_INFO header
 

	
 
        :param environ: environ where PATH_INFO is stored
 
        """
 
        try:
 
            environ['PATH_INFO'] = self._get_by_id(environ['PATH_INFO'])
 
            repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
 
            if repo_name.endswith('/'):
 
                repo_name = repo_name.rstrip('/')
 
        except:
 
            log.error(traceback.format_exc())
 
            raise
 

	
 
        return repo_name
 

	
 
    def __get_user(self, username):
 
        return User.get_by_username(username)
 

	
 
    def __get_action(self, environ):
 
        """
 
        Maps mercurial request commands into a clone,pull or push command.
 
        This should always return a valid command string
 

	
 
        :param environ:
 
        """
 
        mapping = {'changegroup': 'pull',
 
                   'changegroupsubset': 'pull',
 
                   'stream_out': 'pull',
 
                   'listkeys': 'pull',
 
                   'unbundle': 'push',
 
                   'pushkey': 'push', }
 
        for qry in environ['QUERY_STRING'].split('&'):
 
            if qry.startswith('cmd'):
 
                cmd = qry.split('=')[-1]
 
                if cmd in mapping:
 
                    return mapping[cmd]
 
                else:
 
                    return 'pull'
 

	
 
    def __inject_extras(self, repo_path, baseui, extras={}):
 
        """
 
        Injects some extra params into baseui instance
 

	
 
        also overwrites global settings with those takes from local hgrc file
 

	
 
        :param baseui: baseui instance
 
        :param extras: dict with extra params to put into baseui
 
        """
 

	
 
        hgrc = os.path.join(repo_path, '.hg', 'hgrc')
 

	
 
        # make our hgweb quiet so it doesn't print output
 
        baseui.setconfig('ui', 'quiet', 'true')
 

	
 
        #inject some additional parameters that will be available in ui
 
        #for hooks
 
        for k, v in extras.items():
 
            baseui.setconfig('rhodecode_extras', k, v)
 

	
 
        repoui = make_ui('file', hgrc, False)
 

	
 
        if repoui:
rhodecode/lib/utils.py
Show inline comments
 
@@ -57,193 +57,193 @@ from rhodecode.model.meta import Session
 
from rhodecode.model.repos_group import ReposGroupModel
 
from rhodecode.lib.utils2 import safe_str, safe_unicode
 
from rhodecode.lib.vcs.utils.fakemod import create_module
 

	
 
log = logging.getLogger(__name__)
 

	
 
REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}__.*')
 

	
 

	
 
def recursive_replace(str_, replace=' '):
 
    """
 
    Recursive replace of given sign to just one instance
 

	
 
    :param str_: given string
 
    :param replace: char to find and replace multiple instances
 

	
 
    Examples::
 
    >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
 
    'Mighty-Mighty-Bo-sstones'
 
    """
 

	
 
    if str_.find(replace * 2) == -1:
 
        return str_
 
    else:
 
        str_ = str_.replace(replace * 2, replace)
 
        return recursive_replace(str_, replace)
 

	
 

	
 
def repo_name_slug(value):
 
    """
 
    Return slug of name of repository
 
    This function is called on each creation/modification
 
    of repository to prevent bad names in repo
 
    """
 

	
 
    slug = remove_formatting(value)
 
    slug = strip_tags(slug)
 

	
 
    for c in """=[]\;'"<>,/~!@#$%^&*()+{}|: """:
 
        slug = slug.replace(c, '-')
 
    slug = recursive_replace(slug, '-')
 
    slug = collapse(slug, '-')
 
    return slug
 

	
 

	
 
def get_repo_slug(request):
 
    _repo = request.environ['pylons.routes_dict'].get('repo_name')
 
    if _repo:
 
        _repo = _repo.rstrip('/')
 
    return _repo
 

	
 

	
 
def get_repos_group_slug(request):
 
    _group = request.environ['pylons.routes_dict'].get('group_name')
 
    if _group:
 
        _group = _group.rstrip('/')
 
    return _group
 

	
 

	
 
def action_logger(user, action, repo, ipaddr='', sa=None, commit=False):
 
    """
 
    Action logger for various actions made by users
 

	
 
    :param user: user that made this action, can be a unique username string or
 
        object containing user_id attribute
 
    :param action: action to log, should be on of predefined unique actions for
 
        easy translations
 
    :param repo: string name of repository or object containing repo_id,
 
        that action was made on
 
    :param ipaddr: optional ip address from what the action was made
 
    :param sa: optional sqlalchemy session
 

	
 
    """
 

	
 
    if not sa:
 
        sa = meta.Session
 

	
 
    try:
 
        if hasattr(user, 'user_id'):
 
            user_obj = user
 
        elif isinstance(user, basestring):
 
            user_obj = User.get_by_username(user)
 
        else:
 
            raise Exception('You have to provide user object or username')
 

	
 
        if hasattr(repo, 'repo_id'):
 
            repo_obj = Repository.get(repo.repo_id)
 
            repo_name = repo_obj.repo_name
 
        elif  isinstance(repo, basestring):
 
            repo_name = repo.lstrip('/')
 
            repo_obj = Repository.get_by_repo_name(repo_name)
 
        else:
 
            raise Exception('You have to provide repository to action logger')
 

	
 
        user_log = UserLog()
 
        user_log.user_id = user_obj.user_id
 
        user_log.action = action
 
        user_log.action = safe_unicode(action)
 

	
 
        user_log.repository_id = repo_obj.repo_id
 
        user_log.repository_name = repo_name
 

	
 
        user_log.action_date = datetime.datetime.now()
 
        user_log.user_ip = ipaddr
 
        sa.add(user_log)
 

	
 
        log.info(
 
            'Adding user %s, action %s on %s' % (user_obj, action,
 
                                                 safe_unicode(repo))
 
        )
 
        if commit:
 
            sa.commit()
 
    except:
 
        log.error(traceback.format_exc())
 
        raise
 

	
 

	
 
def get_repos(path, recursive=False):
 
    """
 
    Scans given path for repos and return (name,(type,path)) tuple
 

	
 
    :param path: path to scan for repositories
 
    :param recursive: recursive search and return names with subdirs in front
 
    """
 

	
 
    # remove ending slash for better results
 
    path = path.rstrip(os.sep)
 

	
 
    def _get_repos(p):
 
        if not os.access(p, os.W_OK):
 
            return
 
        for dirpath in os.listdir(p):
 
            if os.path.isfile(os.path.join(p, dirpath)):
 
                continue
 
            cur_path = os.path.join(p, dirpath)
 
            try:
 
                scm_info = get_scm(cur_path)
 
                yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
 
            except VCSError:
 
                if not recursive:
 
                    continue
 
                #check if this dir containts other repos for recursive scan
 
                rec_path = os.path.join(p, dirpath)
 
                if os.path.isdir(rec_path):
 
                    for inner_scm in _get_repos(rec_path):
 
                        yield inner_scm
 

	
 
    return _get_repos(path)
 

	
 

	
 
def is_valid_repo(repo_name, base_path):
 
    """
 
    Returns True if given path is a valid repository False otherwise
 

	
 
    :param repo_name:
 
    :param base_path:
 

	
 
    :return True: if given path is a valid repository
 
    """
 
    full_path = os.path.join(safe_str(base_path), safe_str(repo_name))
 

	
 
    try:
 
        get_scm(full_path)
 
        return True
 
    except VCSError:
 
        return False
 

	
 

	
 
def is_valid_repos_group(repos_group_name, base_path):
 
    """
 
    Returns True if given path is a repos group False otherwise
 

	
 
    :param repo_name:
 
    :param base_path:
 
    """
 
    full_path = os.path.join(safe_str(base_path), safe_str(repos_group_name))
 

	
 
    # check if it's not a repo
 
    if is_valid_repo(repos_group_name, base_path):
 
        return False
 

	
 
    # check if it's a valid path
 
    if os.path.isdir(full_path):
 
        return True
 

	
 
    return False
 

	
 

	
 
def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
 
    while True:
 
        ok = raw_input(prompt)
 
        if ok in ('y', 'ye', 'yes'):
 
            return True
 
        if ok in ('n', 'no', 'nop', 'nope'):
rhodecode/lib/utils2.py
Show inline comments
 
@@ -299,107 +299,110 @@ def age(curdate):
 

	
 
    _ = lambda s: s
 

	
 
    if not curdate:
 
        return ''
 

	
 
    agescales = [(_(u"year"), 3600 * 24 * 365),
 
                 (_(u"month"), 3600 * 24 * 30),
 
                 (_(u"day"), 3600 * 24),
 
                 (_(u"hour"), 3600),
 
                 (_(u"minute"), 60),
 
                 (_(u"second"), 1), ]
 

	
 
    age = datetime.now() - curdate
 
    age_seconds = (age.days * agescales[2][1]) + age.seconds
 
    pos = 1
 
    for scale in agescales:
 
        if scale[1] <= age_seconds:
 
            if pos == 6:
 
                pos = 5
 
            return '%s %s' % (time_ago_in_words(curdate,
 
                                                agescales[pos][0]), _('ago'))
 
        pos += 1
 

	
 
    return _(u'just now')
 

	
 

	
 
def uri_filter(uri):
 
    """
 
    Removes user:password from given url string
 

	
 
    :param uri:
 
    :rtype: unicode
 
    :returns: filtered list of strings
 
    """
 
    if not uri:
 
        return ''
 

	
 
    proto = ''
 

	
 
    for pat in ('https://', 'http://'):
 
        if uri.startswith(pat):
 
            uri = uri[len(pat):]
 
            proto = pat
 
            break
 

	
 
    # remove passwords and username
 
    uri = uri[uri.find('@') + 1:]
 

	
 
    # get the port
 
    cred_pos = uri.find(':')
 
    if cred_pos == -1:
 
        host, port = uri, None
 
    else:
 
        host, port = uri[:cred_pos], uri[cred_pos + 1:]
 

	
 
    return filter(None, [proto, host, port])
 

	
 

	
 
def credentials_filter(uri):
 
    """
 
    Returns a url with removed credentials
 

	
 
    :param uri:
 
    """
 

	
 
    uri = uri_filter(uri)
 
    #check if we have port
 
    if len(uri) > 2 and uri[2]:
 
        uri[2] = ':' + uri[2]
 

	
 
    return ''.join(uri)
 

	
 

	
 
def get_changeset_safe(repo, rev):
 
    """
 
    Safe version of get_changeset if this changeset doesn't exists for a
 
    repo it returns a Dummy one instead
 

	
 
    :param repo:
 
    :param rev:
 
    """
 
    from rhodecode.lib.vcs.backends.base import BaseRepository
 
    from rhodecode.lib.vcs.exceptions import RepositoryError
 
    if not isinstance(repo, BaseRepository):
 
        raise Exception('You must pass an Repository '
 
                        'object as first argument got %s', type(repo))
 

	
 
    try:
 
        cs = repo.get_changeset(rev)
 
    except RepositoryError:
 
        from rhodecode.lib.utils import EmptyChangeset
 
        cs = EmptyChangeset(requested_revision=rev)
 
    return cs
 

	
 

	
 
MENTIONS_REGEX = r'(?:^@|\s@)([a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+)(?:\s{1})'
 

	
 

	
 
def extract_mentioned_users(s):
 
    """
 
    Returns unique usernames from given string s that have @mention
 

	
 
    :param s: string to get mentions
 
    """
 
    usrs = {}
 
    for username in re.findall(r'(?:^@|\s@)(\w+)', s):
 
        usrs[username] = username
 
    usrs = set()
 
    for username in re.findall(MENTIONS_REGEX, s):
 
        usrs.add(username)
 

	
 
    return sorted(usrs.keys())
 
    return sorted(list(usrs), key=lambda k: k.lower())
rhodecode/lib/vcs/backends/base.py
Show inline comments
 
@@ -816,96 +816,141 @@ class BaseInMemoryChangeset(object):
 
        """
 
        Checks in-memory changeset's integrity. Also, sets parents if not
 
        already set.
 

	
 
        :raises CommitError: if any error occurs (i.e.
 
          ``NodeDoesNotExistError``).
 
        """
 
        if not self.parents:
 
            parents = parents or []
 
            if len(parents) == 0:
 
                try:
 
                    parents = [self.repository.get_changeset(), None]
 
                except EmptyRepositoryError:
 
                    parents = [None, None]
 
            elif len(parents) == 1:
 
                parents += [None]
 
            self.parents = parents
 

	
 
        # Local parents, only if not None
 
        parents = [p for p in self.parents if p]
 

	
 
        # Check nodes marked as added
 
        for p in parents:
 
            for node in self.added:
 
                try:
 
                    p.get_node(node.path)
 
                except NodeDoesNotExistError:
 
                    pass
 
                else:
 
                    raise NodeAlreadyExistsError("Node at %s already exists "
 
                        "at %s" % (node.path, p))
 

	
 
        # Check nodes marked as changed
 
        missing = set(self.changed)
 
        not_changed = set(self.changed)
 
        if self.changed and not parents:
 
            raise NodeDoesNotExistError(str(self.changed[0].path))
 
        for p in parents:
 
            for node in self.changed:
 
                try:
 
                    old = p.get_node(node.path)
 
                    missing.remove(node)
 
                    if old.content != node.content:
 
                        not_changed.remove(node)
 
                except NodeDoesNotExistError:
 
                    pass
 
        if self.changed and missing:
 
            raise NodeDoesNotExistError("Node at %s is missing "
 
                "(parents: %s)" % (node.path, parents))
 

	
 
        if self.changed and not_changed:
 
            raise NodeNotChangedError("Node at %s wasn't actually changed "
 
                "since parents' changesets: %s" % (not_changed.pop().path,
 
                    parents)
 
            )
 

	
 
        # Check nodes marked as removed
 
        if self.removed and not parents:
 
            raise NodeDoesNotExistError("Cannot remove node at %s as there "
 
                "were no parents specified" % self.removed[0].path)
 
        really_removed = set()
 
        for p in parents:
 
            for node in self.removed:
 
                try:
 
                    p.get_node(node.path)
 
                    really_removed.add(node)
 
                except ChangesetError:
 
                    pass
 
        not_removed = set(self.removed) - really_removed
 
        if not_removed:
 
            raise NodeDoesNotExistError("Cannot remove node at %s from "
 
                "following parents: %s" % (not_removed[0], parents))
 

	
 
    def commit(self, message, author, parents=None, branch=None, date=None,
 
            **kwargs):
 
        """
 
        Performs in-memory commit (doesn't check workdir in any way) and
 
        returns newly created ``Changeset``. Updates repository's
 
        ``revisions``.
 

	
 
        .. note::
 
            While overriding this method each backend's should call
 
            ``self.check_integrity(parents)`` in the first place.
 

	
 
        :param message: message of the commit
 
        :param author: full username, i.e. "Joe Doe <joe.doe@example.com>"
 
        :param parents: single parent or sequence of parents from which commit
 
          would be derieved
 
        :param date: ``datetime.datetime`` instance. Defaults to
 
          ``datetime.datetime.now()``.
 
        :param branch: branch name, as string. If none given, default backend's
 
          branch would be used.
 

	
 
        :raises ``CommitError``: if any error occurs while committing
 
        """
 
        raise NotImplementedError
 

	
 

	
 
class EmptyChangeset(BaseChangeset):
 
    """
 
    An dummy empty changeset. It's possible to pass hash when creating
 
    an EmptyChangeset
 
    """
 

	
 
    def __init__(self, cs='0' * 40, repo=None, requested_revision=None,
 
                 alias=None):
 
        self._empty_cs = cs
 
        self.revision = -1
 
        self.message = ''
 
        self.author = ''
 
        self.date = ''
 
        self.repository = repo
 
        self.requested_revision = requested_revision
 
        self.alias = alias
 

	
 
    @LazyProperty
 
    def raw_id(self):
 
        """
 
        Returns raw string identifying this changeset, useful for web
 
        representation.
 
        """
 

	
 
        return self._empty_cs
 

	
 
    @LazyProperty
 
    def branch(self):
 
        from rhodecode.lib.vcs.backends import get_backend
 
        return get_backend(self.alias).DEFAULT_BRANCH_NAME
 

	
 
    @LazyProperty
 
    def short_id(self):
 
        return self.raw_id[:12]
 

	
 
    def get_file_changeset(self, path):
 
        return self
 

	
 
    def get_file_content(self, path):
 
        return u''
 

	
 
    def get_file_size(self, path):
 
        return 0
rhodecode/lib/vcs/backends/git/changeset.py
Show inline comments
 
import re
 
from itertools import chain
 
from dulwich import objects
 
from subprocess import Popen, PIPE
 
from rhodecode.lib.vcs.conf import settings
 
from rhodecode.lib.vcs.exceptions import RepositoryError
 
from rhodecode.lib.vcs.exceptions import ChangesetError
 
from rhodecode.lib.vcs.exceptions import NodeDoesNotExistError
 
from rhodecode.lib.vcs.exceptions import VCSError
 
from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError
 
from rhodecode.lib.vcs.exceptions import ImproperArchiveTypeError
 
from rhodecode.lib.vcs.backends.base import BaseChangeset
 
from rhodecode.lib.vcs.nodes import FileNode, DirNode, NodeKind, RootNode, RemovedFileNode
 
from rhodecode.lib.vcs.nodes import FileNode, DirNode, NodeKind, RootNode, \
 
    RemovedFileNode, SubModuleNode
 
from rhodecode.lib.vcs.utils import safe_unicode
 
from rhodecode.lib.vcs.utils import date_fromtimestamp
 
from rhodecode.lib.vcs.utils.lazy import LazyProperty
 

	
 

	
 
class GitChangeset(BaseChangeset):
 
    """
 
    Represents state of the repository at single revision.
 
    """
 

	
 
    def __init__(self, repository, revision):
 
        self._stat_modes = {}
 
        self.repository = repository
 
        self.raw_id = revision
 
        self.revision = repository.revisions.index(revision)
 

	
 
        self.short_id = self.raw_id[:12]
 
        self.id = self.raw_id
 
        try:
 
            commit = self.repository._repo.get_object(self.raw_id)
 
        except KeyError:
 
            raise RepositoryError("Cannot get object with id %s" % self.raw_id)
 
        self._commit = commit
 
        self._tree_id = commit.tree
 

	
 
        try:
 
            self.message = safe_unicode(commit.message[:-1])
 
            # Always strip last eol
 
        except UnicodeDecodeError:
 
            self.message = commit.message[:-1].decode(commit.encoding
 
                or 'utf-8')
 
        #self.branch = None
 
        self.tags = []
 
        #tree = self.repository.get_object(self._tree_id)
 
        self.nodes = {}
 
        self._paths = {}
 

	
 
    @LazyProperty
 
    def author(self):
 
        return safe_unicode(self._commit.committer)
 

	
 
    @LazyProperty
 
    def date(self):
 
        return date_fromtimestamp(self._commit.commit_time,
 
                                  self._commit.commit_timezone)
 

	
 
    @LazyProperty
 
    def status(self):
 
        """
 
        Returns modified, added, removed, deleted files for current changeset
 
        """
 
        return self.changed, self.added, self.removed
 

	
 
    @LazyProperty
 
    def branch(self):
 
        # TODO: Cache as we walk (id <-> branch name mapping)
 
        refs = self.repository._repo.get_refs()
 
        heads = {}
 
        for key, val in refs.items():
 
            for ref_key in ['refs/heads/', 'refs/remotes/origin/']:
 
                if key.startswith(ref_key):
 
                    n = key[len(ref_key):]
 
                    if n not in ['HEAD']:
 
                        heads[n] = val
 

	
 
        heads = self.repository._heads(reverse=False)
 

	
 
        for name, id in heads.iteritems():
 
            walker = self.repository._repo.object_store.get_graph_walker([id])
 
            while True:
 
                id_ = walker.next()
 
                if not id_:
 
                    break
 
                if id_ == self.id:
 
                    return safe_unicode(name)
 
        raise ChangesetError("This should not happen... Have you manually "
 
                             "change id of the changeset?")
 
        ref = heads.get(self.raw_id)
 
        if ref:
 
            return safe_unicode(ref)
 

	
 
    def _fix_path(self, path):
 
        """
 
        Paths are stored without trailing slash so we need to get rid off it if
 
        needed.
 
        """
 
        if path.endswith('/'):
 
            path = path.rstrip('/')
 
        return path
 

	
 
    def _get_id_for_path(self, path):
 

	
 
        # FIXME: Please, spare a couple of minutes and make those codes cleaner;
 
        if not path in self._paths:
 
            path = path.strip('/')
 
            # set root tree
 
            tree = self.repository._repo[self._commit.tree]
 
            if path == '':
 
                self._paths[''] = tree.id
 
                return tree.id
 
            splitted = path.split('/')
 
            dirs, name = splitted[:-1], splitted[-1]
 
            curdir = ''
 

	
 
            # initially extract things from root dir
 
            for item, stat, id in tree.iteritems():
 
                if curdir:
 
                    name = '/'.join((curdir, item))
 
                else:
 
                    name = item
 
                self._paths[name] = id
 
                self._stat_modes[name] = stat
 

	
 
            for dir in dirs:
 
                if curdir:
 
                    curdir = '/'.join((curdir, dir))
 
                else:
 
                    curdir = dir
 
                dir_id = None
 
                for item, stat, id in tree.iteritems():
 
                    if dir == item:
 
                        dir_id = id
 
                if dir_id:
 
                    # Update tree
 
                    tree = self.repository._repo[dir_id]
 
                    if not isinstance(tree, objects.Tree):
 
                        raise ChangesetError('%s is not a directory' % curdir)
 
                else:
 
                    raise ChangesetError('%s have not been found' % curdir)
 

	
 
                # cache all items from the given traversed tree
 
                for item, stat, id in tree.iteritems():
 
                    if curdir:
 
                        name = '/'.join((curdir, item))
 
                    else:
 
                        name = item
 
                    self._paths[name] = id
 
                    self._stat_modes[name] = stat
 

	
 
            if not path in self._paths:
 
                raise NodeDoesNotExistError("There is no file nor directory "
 
                    "at the given path %r at revision %r"
 
                    % (path, self.short_id))
 
        return self._paths[path]
 

	
 
    def _get_kind(self, path):
 
        id = self._get_id_for_path(path)
 
        obj = self.repository._repo[id]
 
        if isinstance(obj, objects.Blob):
 
            return NodeKind.FILE
 
        elif isinstance(obj, objects.Tree):
 
            return NodeKind.DIR
 

	
 
    def _get_file_nodes(self):
 
        return chain(*(t[2] for t in self.walk()))
 

	
 
    @LazyProperty
 
    def parents(self):
 
        """
 
        Returns list of parents changesets.
 
        """
 
        return [self.repository.get_changeset(parent)
 
            for parent in self._commit.parents]
 

	
 
    def next(self, branch=None):
 

	
 
        if branch and self.branch != branch:
 
            raise VCSError('Branch option used on changeset not belonging '
 
                           'to that branch')
 

	
 
        def _next(changeset, branch):
 
            try:
 
                next_ = changeset.revision + 1
 
                next_rev = changeset.repository.revisions[next_]
 
            except IndexError:
 
                raise ChangesetDoesNotExistError
 
            cs = changeset.repository.get_changeset(next_rev)
 

	
 
            if branch and branch != cs.branch:
 
                return _next(cs, branch)
 

	
 
            return cs
 

	
 
        return _next(self, branch)
 

	
 
    def prev(self, branch=None):
 
        if branch and self.branch != branch:
 
            raise VCSError('Branch option used on changeset not belonging '
 
                           'to that branch')
 

	
 
        def _prev(changeset, branch):
 
            try:
 
                prev_ = changeset.revision - 1
 
                if prev_ < 0:
 
                    raise IndexError
 
                prev_rev = changeset.repository.revisions[prev_]
 
            except IndexError:
 
                raise ChangesetDoesNotExistError
 

	
 
            cs = changeset.repository.get_changeset(prev_rev)
 

	
 
            if branch and branch != cs.branch:
 
                return _prev(cs, branch)
 

	
 
            return cs
 

	
 
        return _prev(self, branch)
 

	
 
    def get_file_mode(self, path):
 
        """
 
        Returns stat mode of the file at the given ``path``.
 
        """
 
        # ensure path is traversed
 
        self._get_id_for_path(path)
 
        return self._stat_modes[path]
 

	
 
    def get_file_content(self, path):
 
        """
 
        Returns content of the file at given ``path``.
 
        """
 
        id = self._get_id_for_path(path)
 
        blob = self.repository._repo[id]
 
        return blob.as_pretty_string()
 

	
 
    def get_file_size(self, path):
 
        """
 
        Returns size of the file at given ``path``.
 
        """
 
        id = self._get_id_for_path(path)
 
        blob = self.repository._repo[id]
 
        return blob.raw_length()
 

	
 
    def get_file_changeset(self, path):
 
        """
 
        Returns last commit of the file at the given ``path``.
 
@@ -251,209 +237,224 @@ class GitChangeset(BaseChangeset):
 
        which file at given ``path`` has been modified.
 

	
 
        TODO: This function now uses os underlying 'git' and 'grep' commands
 
        which is generally not good. Should be replaced with algorithm
 
        iterating commits.
 
        """
 
        cmd = 'log --pretty="format: %%H" --name-status -p %s -- "%s"' % (
 
                  self.id, path
 
               )
 
        so, se = self.repository.run_git_command(cmd)
 
        ids = re.findall(r'\w{40}', so)
 
        return [self.repository.get_changeset(id) for id in ids]
 

	
 
    def get_file_annotate(self, path):
 
        """
 
        Returns a list of three element tuples with lineno,changeset and line
 

	
 
        TODO: This function now uses os underlying 'git' command which is
 
        generally not good. Should be replaced with algorithm iterating
 
        commits.
 
        """
 
        cmd = 'blame -l --root -r %s -- "%s"' % (self.id, path)
 
        # -l     ==> outputs long shas (and we need all 40 characters)
 
        # --root ==> doesn't put '^' character for bounderies
 
        # -r sha ==> blames for the given revision
 
        so, se = self.repository.run_git_command(cmd)
 
        annotate = []
 
        for i, blame_line in enumerate(so.split('\n')[:-1]):
 
            ln_no = i + 1
 
            id, line = re.split(r' \(.+?\) ', blame_line, 1)
 
            annotate.append((ln_no, self.repository.get_changeset(id), line))
 
        return annotate
 

	
 
    def fill_archive(self, stream=None, kind='tgz', prefix=None,
 
                     subrepos=False):
 
        """
 
        Fills up given stream.
 

	
 
        :param stream: file like object.
 
        :param kind: one of following: ``zip``, ``tgz`` or ``tbz2``.
 
            Default: ``tgz``.
 
        :param prefix: name of root directory in archive.
 
            Default is repository name and changeset's raw_id joined with dash
 
            (``repo-tip.<KIND>``).
 
        :param subrepos: include subrepos in this archive.
 

	
 
        :raise ImproperArchiveTypeError: If given kind is wrong.
 
        :raise VcsError: If given stream is None
 

	
 
        """
 
        allowed_kinds = settings.ARCHIVE_SPECS.keys()
 
        if kind not in allowed_kinds:
 
            raise ImproperArchiveTypeError('Archive kind not supported use one'
 
                'of %s', allowed_kinds)
 

	
 
        if prefix is None:
 
            prefix = '%s-%s' % (self.repository.name, self.short_id)
 
        elif prefix.startswith('/'):
 
            raise VCSError("Prefix cannot start with leading slash")
 
        elif prefix.strip() == '':
 
            raise VCSError("Prefix cannot be empty")
 

	
 
        if kind == 'zip':
 
            frmt = 'zip'
 
        else:
 
            frmt = 'tar'
 
        cmd = 'git archive --format=%s --prefix=%s/ %s' % (frmt, prefix,
 
            self.raw_id)
 
        if kind == 'tgz':
 
            cmd += ' | gzip -9'
 
        elif kind == 'tbz2':
 
            cmd += ' | bzip2 -9'
 

	
 
        if stream is None:
 
            raise VCSError('You need to pass in a valid stream for filling'
 
                           ' with archival data')
 
        popen = Popen(cmd, stdout=PIPE, stderr=PIPE, shell=True,
 
            cwd=self.repository.path)
 

	
 
        buffer_size = 1024 * 8
 
        chunk = popen.stdout.read(buffer_size)
 
        while chunk:
 
            stream.write(chunk)
 
            chunk = popen.stdout.read(buffer_size)
 
        # Make sure all descriptors would be read
 
        popen.communicate()
 

	
 
    def get_nodes(self, path):
 
        if self._get_kind(path) != NodeKind.DIR:
 
            raise ChangesetError("Directory does not exist for revision %r at "
 
                " %r" % (self.revision, path))
 
        path = self._fix_path(path)
 
        id = self._get_id_for_path(path)
 
        tree = self.repository._repo[id]
 
        dirnodes = []
 
        filenodes = []
 
        als = self.repository.alias
 
        for name, stat, id in tree.iteritems():
 
            if objects.S_ISGITLINK(stat):
 
                dirnodes.append(SubModuleNode(name, url=None, changeset=id,
 
                                              alias=als))
 
                continue
 

	
 
            obj = self.repository._repo.get_object(id)
 
            if path != '':
 
                obj_path = '/'.join((path, name))
 
            else:
 
                obj_path = name
 
            if obj_path not in self._stat_modes:
 
                self._stat_modes[obj_path] = stat
 
            if isinstance(obj, objects.Tree):
 
                dirnodes.append(DirNode(obj_path, changeset=self))
 
            elif isinstance(obj, objects.Blob):
 
                filenodes.append(FileNode(obj_path, changeset=self, mode=stat))
 
            else:
 
                raise ChangesetError("Requested object should be Tree "
 
                                     "or Blob, is %r" % type(obj))
 
        nodes = dirnodes + filenodes
 
        for node in nodes:
 
            if not node.path in self.nodes:
 
                self.nodes[node.path] = node
 
        nodes.sort()
 
        return nodes
 

	
 
    def get_node(self, path):
 
        if isinstance(path, unicode):
 
            path = path.encode('utf-8')
 
        path = self._fix_path(path)
 
        if not path in self.nodes:
 
            try:
 
                id = self._get_id_for_path(path)
 
                id_ = self._get_id_for_path(path)
 
            except ChangesetError:
 
                raise NodeDoesNotExistError("Cannot find one of parents' "
 
                    "directories for a given path: %s" % path)
 
            obj = self.repository._repo.get_object(id)
 

	
 
            als = self.repository.alias
 
            _GL = lambda m: m and objects.S_ISGITLINK(m)
 
            if _GL(self._stat_modes.get(path)):
 
                node = SubModuleNode(path, url=None, changeset=id_, alias=als)
 
            else:
 
                obj = self.repository._repo.get_object(id_)
 

	
 
            if isinstance(obj, objects.Tree):
 
                if path == '':
 
                    node = RootNode(changeset=self)
 
                else:
 
                    node = DirNode(path, changeset=self)
 
                node._tree = obj
 
            elif isinstance(obj, objects.Blob):
 
                node = FileNode(path, changeset=self)
 
                node._blob = obj
 
            else:
 
                raise NodeDoesNotExistError("There is no file nor directory "
 
                    "at the given path %r at revision %r"
 
                    % (path, self.short_id))
 
            # cache node
 
            self.nodes[path] = node
 
        return self.nodes[path]
 

	
 
    @LazyProperty
 
    def affected_files(self):
 
        """
 
        Get's a fast accessible file changes for given changeset
 
        """
 

	
 
        return self.added + self.changed
 

	
 
    @LazyProperty
 
    def _diff_name_status(self):
 
        output = []
 
        for parent in self.parents:
 
            cmd = 'diff --name-status %s %s' % (parent.raw_id, self.raw_id)
 
            cmd = 'diff --name-status %s %s --encoding=utf8' % (parent.raw_id, self.raw_id)
 
            so, se = self.repository.run_git_command(cmd)
 
            output.append(so.strip())
 
        return '\n'.join(output)
 

	
 
    def _get_paths_for_status(self, status):
 
        """
 
        Returns sorted list of paths for given ``status``.
 

	
 
        :param status: one of: *added*, *modified* or *deleted*
 
        """
 
        paths = set()
 
        char = status[0].upper()
 
        for line in self._diff_name_status.splitlines():
 
            if not line:
 
                continue
 

	
 
            if line.startswith(char):
 
                splitted = line.split(char,1)
 
                if not len(splitted) == 2:
 
                    raise VCSError("Couldn't parse diff result:\n%s\n\n and "
 
                        "particularly that line: %s" % (self._diff_name_status,
 
                        line))
 
                paths.add(splitted[1].strip())
 
                _path = splitted[1].strip()
 
                paths.add(_path)
 
        return sorted(paths)
 

	
 
    @LazyProperty
 
    def added(self):
 
        """
 
        Returns list of added ``FileNode`` objects.
 
        """
 
        if not self.parents:
 
            return list(self._get_file_nodes())
 
        return [self.get_node(path) for path in self._get_paths_for_status('added')]
 

	
 
    @LazyProperty
 
    def changed(self):
 
        """
 
        Returns list of modified ``FileNode`` objects.
 
        """
 
        if not self.parents:
 
            return []
 
        return [self.get_node(path) for path in self._get_paths_for_status('modified')]
 

	
 
    @LazyProperty
 
    def removed(self):
 
        """
 
        Returns list of removed ``FileNode`` objects.
 
        """
 
        if not self.parents:
 
            return []
 
        return [RemovedFileNode(path) for path in self._get_paths_for_status('deleted')]
rhodecode/lib/vcs/backends/git/inmemory.py
Show inline comments
 
import time
 
import datetime
 
import posixpath
 
from dulwich import objects
 
from dulwich.repo import Repo
 
from rhodecode.lib.vcs.backends.base import BaseInMemoryChangeset
 
from rhodecode.lib.vcs.exceptions import RepositoryError
 
from rhodecode.lib.vcs.utils import safe_str
 

	
 

	
 
class GitInMemoryChangeset(BaseInMemoryChangeset):
 

	
 
    def commit(self, message, author, parents=None, branch=None, date=None,
 
            **kwargs):
 
        """
 
        Performs in-memory commit (doesn't check workdir in any way) and
 
        returns newly created ``Changeset``. Updates repository's
 
        ``revisions``.
 

	
 
        :param message: message of the commit
 
        :param author: full username, i.e. "Joe Doe <joe.doe@example.com>"
 
        :param parents: single parent or sequence of parents from which commit
 
          would be derieved
 
        :param date: ``datetime.datetime`` instance. Defaults to
 
          ``datetime.datetime.now()``.
 
        :param branch: branch name, as string. If none given, default backend's
 
          branch would be used.
 

	
 
        :raises ``CommitError``: if any error occurs while committing
 
        """
 
        self.check_integrity(parents)
 

	
 
        from .repository import GitRepository
 
        if branch is None:
 
            branch = GitRepository.DEFAULT_BRANCH_NAME
 

	
 
        repo = self.repository._repo
 
        object_store = repo.object_store
 

	
 
        ENCODING = "UTF-8"
 
        DIRMOD = 040000
 

	
 
        # Create tree and populates it with blobs
 
        commit_tree = self.parents[0] and repo[self.parents[0]._commit.tree] or\
 
            objects.Tree()
 
        for node in self.added + self.changed:
 
            # Compute subdirs if needed
 
            dirpath, nodename = posixpath.split(node.path)
 
            dirnames = dirpath and dirpath.split('/') or []
 
            parent = commit_tree
 
            ancestors = [('', parent)]
 

	
 
            # Tries to dig for the deepest existing tree
 
            while dirnames:
 
                curdir = dirnames.pop(0)
 
                try:
 
                    dir_id = parent[curdir][1]
 
                except KeyError:
 
                    # put curdir back into dirnames and stops
 
                    dirnames.insert(0, curdir)
 
                    break
 
                else:
 
                    # If found, updates parent
 
                    parent = self.repository._repo[dir_id]
 
                    ancestors.append((curdir, parent))
 
            # Now parent is deepest exising tree and we need to create subtrees
 
            # for dirnames (in reverse order) [this only applies for nodes from added]
 
            new_trees = []
 
            blob = objects.Blob.from_string(node.content.encode(ENCODING))
 
            node_path = node.name.encode(ENCODING)
 
            if dirnames:
 
                # If there are trees which should be created we need to build
 
                # them now (in reverse order)
 
                reversed_dirnames = list(reversed(dirnames))
 
                curtree = objects.Tree()
 
                curtree[node_path] = node.mode, blob.id
 
                new_trees.append(curtree)
 
                for dirname in reversed_dirnames[:-1]:
 
                    newtree = objects.Tree()
 
                    #newtree.add(DIRMOD, dirname, curtree.id)
 
                    newtree[dirname] = DIRMOD, curtree.id
 
                    new_trees.append(newtree)
 
                    curtree = newtree
 
                parent[reversed_dirnames[-1]] = DIRMOD, curtree.id
 
            else:
 
                parent.add(node.mode, node_path, blob.id)
 
            new_trees.append(parent)
 
            # Update ancestors
 
            for parent, tree, path in reversed([(a[1], b[1], b[0]) for a, b in
 
                zip(ancestors, ancestors[1:])]):
 
                parent[path] = DIRMOD, tree.id
 
                object_store.add_object(tree)
 

	
 
            object_store.add_object(blob)
 
            for tree in new_trees:
 
                object_store.add_object(tree)
 
        for node in self.removed:
 
            paths = node.path.split('/')
 
            tree = commit_tree
 
            trees = [tree]
 
            # Traverse deep into the forest...
 
            for path in paths:
 
                try:
 
                    obj = self.repository._repo[tree[path][1]]
 
                    if isinstance(obj, objects.Tree):
 
                        trees.append(obj)
 
                        tree = obj
 
                except KeyError:
 
                    break
 
            # Cut down the blob and all rotten trees on the way back...
 
            for path, tree in reversed(zip(paths, trees)):
 
                del tree[path]
 
                if tree:
 
                    # This tree still has elements - don't remove it or any
 
                    # of it's parents
 
                    break
 

	
 
        object_store.add_object(commit_tree)
 

	
 
        # Create commit
 
        commit = objects.Commit()
 
        commit.tree = commit_tree.id
 
        commit.parents = [p._commit.id for p in self.parents if p]
 
        commit.author = commit.committer = author
 
        commit.author = commit.committer = safe_str(author)
 
        commit.encoding = ENCODING
 
        commit.message = message + ' '
 
        commit.message = safe_str(message) + ' '
 

	
 
        # Compute date
 
        if date is None:
 
            date = time.time()
 
        elif isinstance(date, datetime.datetime):
 
            date = time.mktime(date.timetuple())
 

	
 
        author_time = kwargs.pop('author_time', date)
 
        commit.commit_time = int(date)
 
        commit.author_time = int(author_time)
 
        tz = time.timezone
 
        author_tz = kwargs.pop('author_timezone', tz)
 
        commit.commit_timezone = tz
 
        commit.author_timezone = author_tz
 

	
 
        object_store.add_object(commit)
 

	
 
        ref = 'refs/heads/%s' % branch
 
        repo.refs[ref] = commit.id
 
        repo.refs.set_symbolic_ref('HEAD', ref)
 

	
 
        # Update vcs repository object & recreate dulwich repo
 
        self.repository.revisions.append(commit.id)
 
        self.repository._repo = Repo(self.repository.path)
 
        tip = self.repository.get_changeset()
 
        self.reset()
 
        return tip
 

	
 
    def _get_missing_trees(self, path, root_tree):
 
        """
 
        Creates missing ``Tree`` objects for the given path.
 

	
 
        :param path: path given as a string. It may be a path to a file node
 
          (i.e. ``foo/bar/baz.txt``) or directory path - in that case it must
 
          end with slash (i.e. ``foo/bar/``).
 
        :param root_tree: ``dulwich.objects.Tree`` object from which we start
 
          traversing (should be commit's root tree)
 
        """
 
        dirpath = posixpath.split(path)[0]
 
        dirs = dirpath.split('/')
 
        if not dirs or dirs == ['']:
 
            return []
 

	
 
        def get_tree_for_dir(tree, dirname):
 
            for name, mode, id in tree.iteritems():
 
                if name == dirname:
 
                    obj = self.repository._repo[id]
 
                    if isinstance(obj, objects.Tree):
 
                        return obj
 
                    else:
 
                        raise RepositoryError("Cannot create directory %s "
 
                        "at tree %s as path is occupied and is not a "
 
                        "Tree" % (dirname, tree))
 
            return None
 

	
 
        trees = []
 
        parent = root_tree
 
        for dirname in dirs:
 
            tree = get_tree_for_dir(parent, dirname)
 
            if tree is None:
 
                tree = objects.Tree()
 
                dirmode = 040000
 
                parent.add(dirmode, dirname, tree.id)
 
                parent = tree
 
            # Always append tree
 
            trees.append(tree)
 
        return trees
rhodecode/lib/vcs/backends/git/repository.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
"""
 
    vcs.backends.git
 
    ~~~~~~~~~~~~~~~~
 

	
 
    Git backend implementation.
 

	
 
    :created_on: Apr 8, 2010
 
    :copyright: (c) 2010-2011 by Marcin Kuzminski, Lukasz Balcerzak.
 
"""
 

	
 
import os
 
import re
 
import time
 
import posixpath
 
from dulwich.repo import Repo, NotGitRepository
 
#from dulwich.config import ConfigFile
 
from string import Template
 
from subprocess import Popen, PIPE
 
from rhodecode.lib.vcs.backends.base import BaseRepository
 
from rhodecode.lib.vcs.exceptions import BranchDoesNotExistError
 
from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError
 
from rhodecode.lib.vcs.exceptions import EmptyRepositoryError
 
from rhodecode.lib.vcs.exceptions import RepositoryError
 
from rhodecode.lib.vcs.exceptions import TagAlreadyExistError
 
from rhodecode.lib.vcs.exceptions import TagDoesNotExistError
 
from rhodecode.lib.vcs.utils import safe_unicode, makedate, date_fromtimestamp
 
from rhodecode.lib.vcs.utils.lazy import LazyProperty
 
from rhodecode.lib.vcs.utils.ordered_dict import OrderedDict
 
from rhodecode.lib.vcs.utils.paths import abspath
 
from rhodecode.lib.vcs.utils.paths import get_user_home
 
from .workdir import GitWorkdir
 
from .changeset import GitChangeset
 
from .inmemory import GitInMemoryChangeset
 
from .config import ConfigFile
 

	
 

	
 
class GitRepository(BaseRepository):
 
    """
 
    Git repository backend.
 
    """
 
    DEFAULT_BRANCH_NAME = 'master'
 
    scm = 'git'
 

	
 
    def __init__(self, repo_path, create=False, src_url=None,
 
                 update_after_clone=False, bare=False):
 

	
 
        self.path = abspath(repo_path)
 
        self._repo = self._get_repo(create, src_url, update_after_clone, bare)
 
        #temporary set that to now at later we will move it to constructor
 
        baseui = None
 
        if baseui is None:
 
            from mercurial.ui import ui
 
            baseui = ui()
 
        # patch the instance of GitRepo with an "FAKE" ui object to add
 
        # compatibility layer with Mercurial
 
        setattr(self._repo, 'ui', baseui)
 

	
 
        try:
 
            self.head = self._repo.head()
 
        except KeyError:
 
            self.head = None
 

	
 
        self._config_files = [
 
            bare and abspath(self.path, 'config') or abspath(self.path, '.git',
 
                'config'),
 
            abspath(get_user_home(), '.gitconfig'),
 
        ]
 

	
 
    @LazyProperty
 
    def revisions(self):
 
        """
 
        Returns list of revisions' ids, in ascending order.  Being lazy
 
        attribute allows external tools to inject shas from cache.
 
        """
 
        return self._get_all_revisions()
 

	
 
    def run_git_command(self, cmd):
 
        """
 
        Runs given ``cmd`` as git command and returns tuple
 
        (returncode, stdout, stderr).
 

	
 
        .. note::
 
           This method exists only until log/blame functionality is implemented
 
           at Dulwich (see https://bugs.launchpad.net/bugs/645142). Parsing
 
           os command's output is road to hell...
 

	
 
        :param cmd: git command to be executed
 
        """
 
        #cmd = '(cd %s && git %s)' % (self.path, cmd)
 

	
 
        _copts = ['-c', 'core.quotepath=false', ]
 
        _str_cmd = False
 
        if isinstance(cmd, basestring):
 
            cmd = 'git %s' % cmd
 
        else:
 
            cmd = ['git'] + cmd
 
            cmd = [cmd]
 
            _str_cmd = True
 

	
 
        cmd = ['GIT_CONFIG_NOGLOBAL=1', 'git'] + _copts + cmd
 
        if _str_cmd:
 
            cmd = ' '.join(cmd)
 
        try:
 
            opts = dict(
 
                shell=isinstance(cmd, basestring),
 
                stdout=PIPE,
 
                stderr=PIPE)
 
            if os.path.isdir(self.path):
 
                opts['cwd'] = self.path
 
            p = Popen(cmd, **opts)
 
        except OSError, err:
 
            raise RepositoryError("Couldn't run git command (%s).\n"
 
                "Original error was:%s" % (cmd, err))
 
        so, se = p.communicate()
 
        if not se.startswith("fatal: bad default revision 'HEAD'") and \
 
            p.returncode != 0:
 
            raise RepositoryError("Couldn't run git command (%s).\n"
 
                "stderr:\n%s" % (cmd, se))
 
        return so, se
 

	
 
    def _check_url(self, url):
 
        """
 
        Functon will check given url and try to verify if it's a valid
 
        link. Sometimes it may happened that mercurial will issue basic
 
        auth request that can cause whole API to hang when used from python
 
        or other external calls.
 

	
 
        On failures it'll raise urllib2.HTTPError
 
        """
 

	
 
        #TODO: implement this
 
        pass
 

	
 
    def _get_repo(self, create, src_url=None, update_after_clone=False,
 
            bare=False):
 
        if create and os.path.exists(self.path):
 
            raise RepositoryError("Location already exist")
 
        if src_url and not create:
 
            raise RepositoryError("Create should be set to True if src_url is "
 
                                  "given (clone operation creates repository)")
 
        try:
 
            if create and src_url:
 
                self._check_url(src_url)
 
                self.clone(src_url, update_after_clone, bare)
 
                return Repo(self.path)
 
            elif create:
 
                os.mkdir(self.path)
 
                if bare:
 
                    return Repo.init_bare(self.path)
 
                else:
 
                    return Repo.init(self.path)
 
            else:
 
                return Repo(self.path)
 
        except (NotGitRepository, OSError), err:
 
            raise RepositoryError(err)
 

	
 
    def _get_all_revisions(self):
 
        cmd = 'rev-list --all --date-order'
 
        try:
 
            so, se = self.run_git_command(cmd)
 
        except RepositoryError:
 
            # Can be raised for empty repositories
 
            return []
 
        revisions = so.splitlines()
 
        revisions.reverse()
 
        return revisions
 

	
 
    def _get_revision(self, revision):
 
        """
 
        For git backend we always return integer here. This way we ensure
 
        that changset's revision attribute would become integer.
 
        """
 
        pattern = re.compile(r'^[[0-9a-fA-F]{12}|[0-9a-fA-F]{40}]$')
 
        is_bstr = lambda o: isinstance(o, (str, unicode))
 
        is_null = lambda o: len(o) == revision.count('0')
 

	
 
        if len(self.revisions) == 0:
 
            raise EmptyRepositoryError("There are no changesets yet")
 

	
 
        if revision in (None, '', 'tip', 'HEAD', 'head', -1):
 
            revision = self.revisions[-1]
 

	
 
        if ((is_bstr(revision) and revision.isdigit() and len(revision) < 12)
 
            or isinstance(revision, int) or is_null(revision)):
 
            try:
 
                revision = self.revisions[int(revision)]
 
            except:
 
                raise ChangesetDoesNotExistError("Revision %r does not exist "
 
                    "for this repository %s" % (revision, self))
 

	
 
        elif is_bstr(revision):
 
            if not pattern.match(revision) or revision not in self.revisions:
 
                raise ChangesetDoesNotExistError("Revision %r does not exist "
 
                    "for this repository %s" % (revision, self))
 

	
 
        # Ensure we return full id
 
        if not pattern.match(str(revision)):
 
            raise ChangesetDoesNotExistError("Given revision %r not recognized"
 
                % revision)
 
        return revision
 

	
 
    def _get_archives(self, archive_name='tip'):
 

	
 
        for i in [('zip', '.zip'), ('gz', '.tar.gz'), ('bz2', '.tar.bz2')]:
 
                yield {"type": i[0], "extension": i[1], "node": archive_name}
 

	
 
    def _get_url(self, url):
 
        """
 
        Returns normalized url. If schema is not given, would fall to
 
        filesystem (``file:///``) schema.
 
        """
 
        url = str(url)
 
        if url != 'default' and not '://' in url:
 
            url = ':///'.join(('file', url))
 
        return url
 

	
 
    @LazyProperty
 
    def name(self):
 
        return os.path.basename(self.path)
 

	
 
    @LazyProperty
 
    def last_change(self):
 
        """
 
        Returns last change made on this repository as datetime object
 
        """
 
        return date_fromtimestamp(self._get_mtime(), makedate()[1])
 

	
 
    def _get_mtime(self):
 
        try:
 
            return time.mktime(self.get_changeset().date.timetuple())
 
        except RepositoryError:
 
            # fallback to filesystem
 
            in_path = os.path.join(self.path, '.git', "index")
 
            he_path = os.path.join(self.path, '.git', "HEAD")
 
            if os.path.exists(in_path):
 
                return os.stat(in_path).st_mtime
 
            else:
 
                return os.stat(he_path).st_mtime
 

	
 
    @LazyProperty
 
    def description(self):
 
        undefined_description = u'unknown'
 
        description_path = os.path.join(self.path, '.git', 'description')
 
        if os.path.isfile(description_path):
 
            return safe_unicode(open(description_path).read())
 
        else:
 
            return undefined_description
 

	
 
    @LazyProperty
 
    def contact(self):
 
        undefined_contact = u'Unknown'
 
        return undefined_contact
 

	
 
    @property
 
    def branches(self):
 
        if not self.revisions:
 
            return {}
 
        refs = self._repo.refs.as_dict()
 
        sortkey = lambda ctx: ctx[0]
 
        _branches = [('/'.join(ref.split('/')[2:]), head)
 
            for ref, head in refs.items()
 
            if ref.startswith('refs/heads/') and not ref.endswith('/HEAD')]
 
        return OrderedDict(sorted(_branches, key=sortkey, reverse=False))
 

	
 
    def _heads(self, reverse=False):
 
        refs = self._repo.get_refs()
 
        heads = {}
 

	
 
        for key, val in refs.items():
 
            for ref_key in ['refs/heads/', 'refs/remotes/origin/']:
 
                if key.startswith(ref_key):
 
                    n = key[len(ref_key):]
 
                    if n not in ['HEAD']:
 
                        heads[n] = val
 

	
 
        return heads if reverse else dict((y,x) for x,y in heads.iteritems())
 

	
 
    def _get_tags(self):
 
        if not self.revisions:
 
            return {}
 
        sortkey = lambda ctx: ctx[0]
 
        _tags = [('/'.join(ref.split('/')[2:]), head) for ref, head in
 
            self._repo.get_refs().items() if ref.startswith('refs/tags/')]
 
        return OrderedDict(sorted(_tags, key=sortkey, reverse=True))
 

	
 
    @LazyProperty
 
    def tags(self):
 
        return self._get_tags()
 

	
 
    def tag(self, name, user, revision=None, message=None, date=None,
 
            **kwargs):
 
        """
 
        Creates and returns a tag for the given ``revision``.
 

	
 
        :param name: name for new tag
 
        :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
 
        :param revision: changeset id for which new tag would be created
 
        :param message: message of the tag's commit
 
        :param date: date of tag's commit
 

	
 
        :raises TagAlreadyExistError: if tag with same name already exists
 
        """
 
        if name in self.tags:
 
            raise TagAlreadyExistError("Tag %s already exists" % name)
 
        changeset = self.get_changeset(revision)
 
        message = message or "Added tag %s for commit %s" % (name,
 
            changeset.raw_id)
 
        self._repo.refs["refs/tags/%s" % name] = changeset._commit.id
 

	
 
        self.tags = self._get_tags()
 
        return changeset
 

	
 
    def remove_tag(self, name, user, message=None, date=None):
 
        """
 
        Removes tag with the given ``name``.
 

	
 
        :param name: name of the tag to be removed
 
        :param user: full username, i.e.: "Joe Doe <joe.doe@example.com>"
 
        :param message: message of the tag's removal commit
 
        :param date: date of tag's removal commit
 

	
 
        :raises TagDoesNotExistError: if tag with given name does not exists
 
        """
 
        if name not in self.tags:
 
            raise TagDoesNotExistError("Tag %s does not exist" % name)
 
        tagpath = posixpath.join(self._repo.refs.path, 'refs', 'tags', name)
 
        try:
 
            os.remove(tagpath)
 
            self.tags = self._get_tags()
 
        except OSError, e:
 
            raise RepositoryError(e.strerror)
 

	
 
    def get_changeset(self, revision=None):
 
        """
 
        Returns ``GitChangeset`` object representing commit from git repository
 
        at the given revision or head (most recent commit) if None given.
 
        """
 
        if isinstance(revision, GitChangeset):
 
            return revision
 
        revision = self._get_revision(revision)
 
        changeset = GitChangeset(repository=self, revision=revision)
 
        return changeset
 

	
 
    def get_changesets(self, start=None, end=None, start_date=None,
 
           end_date=None, branch_name=None, reverse=False):
 
        """
 
        Returns iterator of ``GitChangeset`` objects from start to end (both
 
        are inclusive), in ascending date order (unless ``reverse`` is set).
 

	
 
        :param start: changeset ID, as str; first returned changeset
 
        :param end: changeset ID, as str; last returned changeset
 
        :param start_date: if specified, changesets with commit date less than
 
          ``start_date`` would be filtered out from returned set
 
        :param end_date: if specified, changesets with commit date greater than
 
          ``end_date`` would be filtered out from returned set
 
        :param branch_name: if specified, changesets not reachable from given
 
          branch would be filtered out from returned set
 
        :param reverse: if ``True``, returned generator would be reversed
 
          (meaning that returned changesets would have descending date order)
 

	
 
        :raise BranchDoesNotExistError: If given ``branch_name`` does not
 
            exist.
 
        :raise ChangesetDoesNotExistError: If changeset for given ``start`` or
 
          ``end`` could not be found.
 

	
 
        """
 
        if branch_name and branch_name not in self.branches:
 
            raise BranchDoesNotExistError("Branch '%s' not found" \
 
                                          % branch_name)
 
        # %H at format means (full) commit hash, initial hashes are retrieved
 
        # in ascending date order
 
        cmd_template = 'log --date-order --reverse --pretty=format:"%H"'
 
        cmd_params = {}
 
@@ -360,148 +387,160 @@ class GitRepository(BaseRepository):
 
        if start:
 
            _start = self._get_revision(start)
 
            try:
 
                start_pos = revs.index(_start)
 
            except ValueError:
 
                pass
 

	
 
        if end is not None:
 
            _end = self._get_revision(end)
 
            try:
 
                end_pos = revs.index(_end)
 
            except ValueError:
 
                pass
 

	
 
        if None not in [start, end] and start_pos > end_pos:
 
            raise RepositoryError('start cannot be after end')
 

	
 
        if end_pos is not None:
 
            end_pos += 1
 

	
 
        revs = revs[start_pos:end_pos]
 
        if reverse:
 
            revs = reversed(revs)
 
        for rev in revs:
 
            yield self.get_changeset(rev)
 

	
 
    def get_diff(self, rev1, rev2, path=None, ignore_whitespace=False,
 
            context=3):
 
        """
 
        Returns (git like) *diff*, as plain text. Shows changes introduced by
 
        ``rev2`` since ``rev1``.
 

	
 
        :param rev1: Entry point from which diff is shown. Can be
 
          ``self.EMPTY_CHANGESET`` - in this case, patch showing all
 
          the changes since empty state of the repository until ``rev2``
 
        :param rev2: Until which revision changes should be shown.
 
        :param ignore_whitespace: If set to ``True``, would not show whitespace
 
          changes. Defaults to ``False``.
 
        :param context: How many lines before/after changed lines should be
 
          shown. Defaults to ``3``.
 
        """
 
        flags = ['-U%s' % context]
 
        if ignore_whitespace:
 
            flags.append('-w')
 

	
 
        if rev1 == self.EMPTY_CHANGESET:
 
            rev2 = self.get_changeset(rev2).raw_id
 
            cmd = ' '.join(['show'] + flags + [rev2])
 
        else:
 
            rev1 = self.get_changeset(rev1).raw_id
 
            rev2 = self.get_changeset(rev2).raw_id
 
            cmd = ' '.join(['diff'] + flags + [rev1, rev2])
 

	
 
        if path:
 
            cmd += ' -- "%s"' % path
 
        stdout, stderr = self.run_git_command(cmd)
 
        # If we used 'show' command, strip first few lines (until actual diff
 
        # starts)
 
        if rev1 == self.EMPTY_CHANGESET:
 
            lines = stdout.splitlines()
 
            x = 0
 
            for line in lines:
 
                if line.startswith('diff'):
 
                    break
 
                x += 1
 
            # Append new line just like 'diff' command do
 
            stdout = '\n'.join(lines[x:]) + '\n'
 
        return stdout
 

	
 
    @LazyProperty
 
    def in_memory_changeset(self):
 
        """
 
        Returns ``GitInMemoryChangeset`` object for this repository.
 
        """
 
        return GitInMemoryChangeset(self)
 

	
 
    def clone(self, url, update_after_clone=True, bare=False):
 
        """
 
        Tries to clone changes from external location.
 

	
 
        :param update_after_clone: If set to ``False``, git won't checkout
 
          working directory
 
        :param bare: If set to ``True``, repository would be cloned into
 
          *bare* git repository (no working directory at all).
 
        """
 
        url = self._get_url(url)
 
        cmd = ['clone']
 
        if bare:
 
            cmd.append('--bare')
 
        elif not update_after_clone:
 
            cmd.append('--no-checkout')
 
        cmd += ['--', '"%s"' % url, '"%s"' % self.path]
 
        cmd = ' '.join(cmd)
 
        # If error occurs run_git_command raises RepositoryError already
 
        self.run_git_command(cmd)
 

	
 
    def pull(self, url):
 
        """
 
        Tries to pull changes from external location.
 
        """
 
        url = self._get_url(url)
 
        cmd = ['pull']
 
        cmd.append("--ff-only")
 
        cmd.append(url)
 
        cmd = ' '.join(cmd)
 
        # If error occurs run_git_command raises RepositoryError already
 
        self.run_git_command(cmd)
 

	
 
    @LazyProperty
 
    def workdir(self):
 
        """
 
        Returns ``Workdir`` instance for this repository.
 
        """
 
        return GitWorkdir(self)
 

	
 
    def get_config_value(self, section, name, config_file=None):
 
        """
 
        Returns configuration value for a given [``section``] and ``name``.
 

	
 
        :param section: Section we want to retrieve value from
 
        :param name: Name of configuration we want to retrieve
 
        :param config_file: A path to file which should be used to retrieve
 
          configuration from (might also be a list of file paths)
 
        """
 
        if config_file is None:
 
            config_file = []
 
        elif isinstance(config_file, basestring):
 
            config_file = [config_file]
 

	
 
        def gen_configs():
 
            for path in config_file + self._config_files:
 
                try:
 
                    yield ConfigFile.from_path(path)
 
                except (IOError, OSError, ValueError):
 
                    continue
 

	
 
        for config in gen_configs():
 
            try:
 
                return config.get(section, name)
 
            except KeyError:
 
                continue
 
        return None
 

	
 
    def get_user_name(self, config_file=None):
 
        """
 
        Returns user's name from global configuration file.
 

	
 
        :param config_file: A path to file which should be used to retrieve
 
          configuration from (might also be a list of file paths)
 
        """
 
        return self.get_config_value('user', 'name', config_file)
 

	
 
    def get_user_email(self, config_file=None):
 
        """
 
        Returns user's email from global configuration file.
 

	
 
        :param config_file: A path to file which should be used to retrieve
 
          configuration from (might also be a list of file paths)
 
        """
 
        return self.get_config_value('user', 'email', config_file)
rhodecode/lib/vcs/backends/hg/changeset.py
Show inline comments
 
import os
 
import posixpath
 

	
 
from rhodecode.lib.vcs.backends.base import BaseChangeset
 
from rhodecode.lib.vcs.conf import settings
 
from rhodecode.lib.vcs.exceptions import  ChangesetDoesNotExistError, \
 
    ChangesetError, ImproperArchiveTypeError, NodeDoesNotExistError, VCSError
 
from rhodecode.lib.vcs.nodes import AddedFileNodesGenerator, ChangedFileNodesGenerator, \
 
    DirNode, FileNode, NodeKind, RemovedFileNodesGenerator, RootNode
 
from rhodecode.lib.vcs.nodes import AddedFileNodesGenerator, \
 
    ChangedFileNodesGenerator, DirNode, FileNode, NodeKind, \
 
    RemovedFileNodesGenerator, RootNode, SubModuleNode
 

	
 
from rhodecode.lib.vcs.utils import safe_str, safe_unicode, date_fromtimestamp
 
from rhodecode.lib.vcs.utils.lazy import LazyProperty
 
from rhodecode.lib.vcs.utils.paths import get_dirs_for_path
 

	
 
from ...utils.hgcompat import archival, hex
 

	
 

	
 
class MercurialChangeset(BaseChangeset):
 
    """
 
    Represents state of the repository at the single revision.
 
    """
 

	
 
    def __init__(self, repository, revision):
 
        self.repository = repository
 
        self.raw_id = revision
 
        self._ctx = repository._repo[revision]
 
        self.revision = self._ctx._rev
 
        self.nodes = {}
 

	
 
    @LazyProperty
 
    def tags(self):
 
        return map(safe_unicode, self._ctx.tags())
 

	
 
    @LazyProperty
 
    def branch(self):
 
        return  safe_unicode(self._ctx.branch())
 

	
 
    @LazyProperty
 
    def bookmarks(self):
 
        return map(safe_unicode, self._ctx.bookmarks())
 

	
 
    @LazyProperty
 
    def message(self):
 
        return safe_unicode(self._ctx.description())
 

	
 
    @LazyProperty
 
    def author(self):
 
        return safe_unicode(self._ctx.user())
 

	
 
    @LazyProperty
 
    def date(self):
 
        return date_fromtimestamp(*self._ctx.date())
 

	
 
    @LazyProperty
 
    def status(self):
 
        """
 
        Returns modified, added, removed, deleted files for current changeset
 
        """
 
        return self.repository._repo.status(self._ctx.p1().node(),
 
                                            self._ctx.node())
 

	
 
    @LazyProperty
 
    def _file_paths(self):
 
        return list(self._ctx)
 

	
 
    @LazyProperty
 
    def _dir_paths(self):
 
        p = list(set(get_dirs_for_path(*self._file_paths)))
 
        p.insert(0, '')
 
        return p
 

	
 
    @LazyProperty
 
    def _paths(self):
 
        return self._dir_paths + self._file_paths
 

	
 
    @LazyProperty
 
    def id(self):
 
        if self.last:
 
            return u'tip'
 
        return self.short_id
 

	
 
    @LazyProperty
 
    def short_id(self):
 
        return self.raw_id[:12]
 

	
 
    @LazyProperty
 
    def parents(self):
 
        """
 
        Returns list of parents changesets.
 
        """
 
        return [self.repository.get_changeset(parent.rev())
 
                for parent in self._ctx.parents() if parent.rev() >= 0]
 

	
 
    def next(self, branch=None):
 

	
 
        if branch and self.branch != branch:
 
            raise VCSError('Branch option used on changeset not belonging '
 
                           'to that branch')
 

	
 
        def _next(changeset, branch):
 
            try:
 
                next_ = changeset.revision + 1
 
                next_rev = changeset.repository.revisions[next_]
 
            except IndexError:
 
                raise ChangesetDoesNotExistError
 
            cs = changeset.repository.get_changeset(next_rev)
 

	
 
            if branch and branch != cs.branch:
 
                return _next(cs, branch)
 

	
 
            return cs
 

	
 
        return _next(self, branch)
 

	
 
    def prev(self, branch=None):
 
        if branch and self.branch != branch:
 
            raise VCSError('Branch option used on changeset not belonging '
 
                           'to that branch')
 

	
 
        def _prev(changeset, branch):
 
            try:
 
                prev_ = changeset.revision - 1
 
                if prev_ < 0:
 
                    raise IndexError
 
                prev_rev = changeset.repository.revisions[prev_]
 
            except IndexError:
 
                raise ChangesetDoesNotExistError
 

	
 
            cs = changeset.repository.get_changeset(prev_rev)
 

	
 
            if branch and branch != cs.branch:
 
                return _prev(cs, branch)
 

	
 
            return cs
 

	
 
        return _prev(self, branch)
 

	
 
    def _fix_path(self, path):
 
        """
 
        Paths are stored without trailing slash so we need to get rid off it if
 
        needed. Also mercurial keeps filenodes as str so we need to decode
 
        from unicode to str
 
        """
 
        if path.endswith('/'):
 
            path = path.rstrip('/')
 

	
 
        return safe_str(path)
 

	
 
    def _get_kind(self, path):
 
        path = self._fix_path(path)
 
        if path in self._file_paths:
 
            return NodeKind.FILE
 
        elif path in self._dir_paths:
 
            return NodeKind.DIR
 
        else:
 
            raise ChangesetError("Node does not exist at the given path %r"
 
                % (path))
 

	
 
    def _get_filectx(self, path):
 
        path = self._fix_path(path)
 
        if self._get_kind(path) != NodeKind.FILE:
 
            raise ChangesetError("File does not exist for revision %r at "
 
                " %r" % (self.revision, path))
 
        return self._ctx.filectx(path)
 

	
 
    def _extract_submodules(self):
 
        """
 
        returns a dictionary with submodule information from substate file
 
        of hg repository
 
        """
 
        return self._ctx.substate
 

	
 
    def get_file_mode(self, path):
 
        """
 
        Returns stat mode of the file at the given ``path``.
 
        """
 
        fctx = self._get_filectx(path)
 
        if 'x' in fctx.flags():
 
            return 0100755
 
        else:
 
            return 0100644
 

	
 
    def get_file_content(self, path):
 
        """
 
        Returns content of the file at given ``path``.
 
        """
 
        fctx = self._get_filectx(path)
 
        return fctx.data()
 

	
 
    def get_file_size(self, path):
 
        """
 
        Returns size of the file at given ``path``.
 
        """
 
        fctx = self._get_filectx(path)
 
        return fctx.size()
 

	
 
    def get_file_changeset(self, path):
 
        """
 
        Returns last commit of the file at the given ``path``.
 
        """
 
        node = self.get_node(path)
 
        return node.history[0]
 

	
 
    def get_file_history(self, path):
 
        """
 
        Returns history of file as reversed list of ``Changeset`` objects for
 
        which file at given ``path`` has been modified.
 
        """
 
        fctx = self._get_filectx(path)
 
        nodes = [fctx.filectx(x).node() for x in fctx.filelog()]
 
        changesets = [self.repository.get_changeset(hex(node))
 
            for node in reversed(nodes)]
 
        return changesets
 

	
 
    def get_file_annotate(self, path):
 
        """
 
        Returns a list of three element tuples with lineno,changeset and line
 
        """
 
        fctx = self._get_filectx(path)
 
        annotate = []
 
        for i, annotate_data in enumerate(fctx.annotate()):
 
            ln_no = i + 1
 
            annotate.append((ln_no, self.repository\
 
                             .get_changeset(hex(annotate_data[0].node())),
 
                             annotate_data[1],))
 

	
 
        return annotate
 

	
 
    def fill_archive(self, stream=None, kind='tgz', prefix=None,
 
                     subrepos=False):
 
        """
 
        Fills up given stream.
 

	
 
        :param stream: file like object.
 
        :param kind: one of following: ``zip``, ``tgz`` or ``tbz2``.
 
            Default: ``tgz``.
 
        :param prefix: name of root directory in archive.
 
            Default is repository name and changeset's raw_id joined with dash
 
            (``repo-tip.<KIND>``).
 
        :param subrepos: include subrepos in this archive.
 

	
 
        :raise ImproperArchiveTypeError: If given kind is wrong.
 
        :raise VcsError: If given stream is None
 
        """
 

	
 
        allowed_kinds = settings.ARCHIVE_SPECS.keys()
 
        if kind not in allowed_kinds:
 
            raise ImproperArchiveTypeError('Archive kind not supported use one'
 
                'of %s', allowed_kinds)
 

	
 
        if stream is None:
 
            raise VCSError('You need to pass in a valid stream for filling'
 
                           ' with archival data')
 

	
 
        if prefix is None:
 
            prefix = '%s-%s' % (self.repository.name, self.short_id)
 
        elif prefix.startswith('/'):
 
            raise VCSError("Prefix cannot start with leading slash")
 
        elif prefix.strip() == '':
 
            raise VCSError("Prefix cannot be empty")
 

	
 
        archival.archive(self.repository._repo, stream, self.raw_id,
 
                         kind, prefix=prefix, subrepos=subrepos)
 

	
 
        #stream.close()
 

	
 
        if stream.closed and hasattr(stream, 'name'):
 
            stream = open(stream.name, 'rb')
 
        elif hasattr(stream, 'mode') and 'r' not in stream.mode:
 
            stream = open(stream.name, 'rb')
 
        else:
 
            stream.seek(0)
 

	
 
    def get_nodes(self, path):
 
        """
 
        Returns combined ``DirNode`` and ``FileNode`` objects list representing
 
        state of changeset at the given ``path``. If node at the given ``path``
 
        is not instance of ``DirNode``, ChangesetError would be raised.
 
        """
 

	
 
        if self._get_kind(path) != NodeKind.DIR:
 
            raise ChangesetError("Directory does not exist for revision %r at "
 
                " %r" % (self.revision, path))
 
        path = self._fix_path(path)
 

	
 
        filenodes = [FileNode(f, changeset=self) for f in self._file_paths
 
            if os.path.dirname(f) == path]
 
        dirs = path == '' and '' or [d for d in self._dir_paths
 
            if d and posixpath.dirname(d) == path]
 
        dirnodes = [DirNode(d, changeset=self) for d in dirs
 
            if os.path.dirname(d) == path]
 

	
 
        als = self.repository.alias
 
        for k, vals in self._extract_submodules().iteritems():
 
            #vals = url,rev,type
 
            loc = vals[0]
 
            cs = vals[1]
 
            dirnodes.append(SubModuleNode(k, url=loc, changeset=cs,
 
                                          alias=als))
 
        nodes = dirnodes + filenodes
 
        # cache nodes
 
        for node in nodes:
 
            self.nodes[node.path] = node
 
        nodes.sort()
 

	
 
        return nodes
 

	
 
    def get_node(self, path):
 
        """
 
        Returns ``Node`` object from the given ``path``. If there is no node at
 
        the given ``path``, ``ChangesetError`` would be raised.
 
        """
 

	
 
        path = self._fix_path(path)
 

	
 
        if not path in self.nodes:
 
            if path in self._file_paths:
 
                node = FileNode(path, changeset=self)
 
            elif path in self._dir_paths or path in self._dir_paths:
 
                if path == '':
 
                    node = RootNode(changeset=self)
 
                else:
 
                    node = DirNode(path, changeset=self)
 
            else:
 
                raise NodeDoesNotExistError("There is no file nor directory "
 
                    "at the given path: %r at revision %r"
 
                    % (path, self.short_id))
 
            # cache node
 
            self.nodes[path] = node
 
        return self.nodes[path]
 

	
 
    @LazyProperty
 
    def affected_files(self):
 
        """
 
        Get's a fast accessible file changes for given changeset
 
        """
 
        return self._ctx.files()
 

	
 
    @property
 
    def added(self):
 
        """
 
        Returns list of added ``FileNode`` objects.
 
        """
 
        return AddedFileNodesGenerator([n for n in self.status[1]], self)
 

	
 
    @property
 
    def changed(self):
 
        """
 
        Returns list of modified ``FileNode`` objects.
 
        """
 
        return ChangedFileNodesGenerator([n for n in  self.status[0]], self)
 

	
 
    @property
 
    def removed(self):
 
        """
 
        Returns list of removed ``FileNode`` objects.
 
        """
 
        return RemovedFileNodesGenerator([n for n in self.status[2]], self)
rhodecode/lib/vcs/backends/hg/inmemory.py
Show inline comments
 
import datetime
 
import errno
 

	
 
from rhodecode.lib.vcs.backends.base import BaseInMemoryChangeset
 
from rhodecode.lib.vcs.exceptions import RepositoryError
 

	
 
from ...utils.hgcompat import memfilectx, memctx, hex
 
from ...utils.hgcompat import memfilectx, memctx, hex, tolocal
 

	
 

	
 
class MercurialInMemoryChangeset(BaseInMemoryChangeset):
 

	
 
    def commit(self, message, author, parents=None, branch=None, date=None,
 
            **kwargs):
 
        """
 
        Performs in-memory commit (doesn't check workdir in any way) and
 
        returns newly created ``Changeset``. Updates repository's
 
        ``revisions``.
 

	
 
        :param message: message of the commit
 
        :param author: full username, i.e. "Joe Doe <joe.doe@example.com>"
 
        :param parents: single parent or sequence of parents from which commit
 
          would be derieved
 
        :param date: ``datetime.datetime`` instance. Defaults to
 
          ``datetime.datetime.now()``.
 
        :param branch: branch name, as string. If none given, default backend's
 
          branch would be used.
 

	
 
        :raises ``CommitError``: if any error occurs while committing
 
        """
 
        self.check_integrity(parents)
 

	
 
        from .repository import MercurialRepository
 
        if not isinstance(message, str) or not isinstance(author, str):
 
        if not isinstance(message, unicode) or not isinstance(author, unicode):
 
            raise RepositoryError('Given message and author needs to be '
 
                                  'an <str> instance')
 
                                  'an <unicode> instance')
 

	
 
        if branch is None:
 
            branch = MercurialRepository.DEFAULT_BRANCH_NAME
 
        kwargs['branch'] = branch
 

	
 
        def filectxfn(_repo, memctx, path):
 
            """
 
            Marks given path as added/changed/removed in a given _repo. This is
 
            for internal mercurial commit function.
 
            """
 

	
 
            # check if this path is removed
 
            if path in (node.path for node in self.removed):
 
                # Raising exception is a way to mark node for removal
 
                raise IOError(errno.ENOENT, '%s is deleted' % path)
 

	
 
            # check if this path is added
 
            for node in self.added:
 
                if node.path == path:
 
                    return memfilectx(path=node.path,
 
                        data=(node.content.encode('utf8')
 
                              if not node.is_binary else node.content),
 
                        islink=False,
 
                        isexec=node.is_executable,
 
                        copied=False)
 

	
 
            # or changed
 
            for node in self.changed:
 
                if node.path == path:
 
                    return memfilectx(path=node.path,
 
                        data=(node.content.encode('utf8')
 
                              if not node.is_binary else node.content),
 
                        islink=False,
 
                        isexec=node.is_executable,
 
                        copied=False)
 

	
 
            raise RepositoryError("Given path haven't been marked as added,"
 
                "changed or removed (%s)" % path)
 

	
 
        parents = [None, None]
 
        for i, parent in enumerate(self.parents):
 
            if parent is not None:
 
                parents[i] = parent._ctx.node()
 

	
 
        if date and isinstance(date, datetime.datetime):
 
            date = date.ctime()
 

	
 
        commit_ctx = memctx(repo=self.repository._repo,
 
            parents=parents,
 
            text='',
 
            files=self.get_paths(),
 
            filectxfn=filectxfn,
 
            user=author,
 
            date=date,
 
            extra=kwargs)
 

	
 
        loc = lambda u: tolocal(u.encode('utf-8'))
 

	
 
        # injecting given _repo params
 
        commit_ctx._text = message
 
        commit_ctx._user = author
 
        commit_ctx._text = loc(message)
 
        commit_ctx._user = loc(author)
 
        commit_ctx._date = date
 

	
 
        # TODO: Catch exceptions!
 
        n = self.repository._repo.commitctx(commit_ctx)
 
        # Returns mercurial node
 
        self._commit_ctx = commit_ctx  # For reference
 
        # Update vcs repository object & recreate mercurial _repo
 
        # new_ctx = self.repository._repo[node]
 
        # new_tip = self.repository.get_changeset(new_ctx.hex())
 
        new_id = hex(n)
 
        self.repository.revisions.append(new_id)
 
        self._repo = self.repository._get_repo(create=False)
 
        self.repository.branches = self.repository._get_branches()
 
        tip = self.repository.get_changeset()
 
        self.reset()
 
        return tip
rhodecode/lib/vcs/nodes.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
"""
 
    vcs.nodes
 
    ~~~~~~~~~
 

	
 
    Module holding everything related to vcs nodes.
 

	
 
    :created_on: Apr 8, 2010
 
    :copyright: (c) 2010-2011 by Marcin Kuzminski, Lukasz Balcerzak.
 
"""
 
import os
 
import stat
 
import posixpath
 
import mimetypes
 

	
 
from pygments import lexers
 

	
 
from rhodecode.lib.vcs.utils.lazy import LazyProperty
 
from rhodecode.lib.vcs.utils import safe_unicode
 
from rhodecode.lib.vcs.utils import safe_unicode, safe_str
 
from rhodecode.lib.vcs.exceptions import NodeError
 
from rhodecode.lib.vcs.exceptions import RemovedFileNodeError
 

	
 
from pygments import lexers
 
from rhodecode.lib.vcs.backends.base import EmptyChangeset
 

	
 

	
 
class NodeKind:
 
    SUBMODULE = -1
 
    DIR = 1
 
    FILE = 2
 

	
 

	
 
class NodeState:
 
    ADDED = u'added'
 
    CHANGED = u'changed'
 
    NOT_CHANGED = u'not changed'
 
    REMOVED = u'removed'
 

	
 

	
 
class NodeGeneratorBase(object):
 
    """
 
    Base class for removed added and changed filenodes, it's a lazy generator
 
    class that will create filenodes only on iteration or call
 

	
 
    The len method doesn't need to create filenodes at all
 
    """
 

	
 
    def __init__(self, current_paths, cs):
 
        self.cs = cs
 
        self.current_paths = current_paths
 

	
 
    def __call__(self):
 
        return [n for n in self]
 

	
 
    def __getslice__(self, i, j):
 
        for p in self.current_paths[i:j]:
 
            yield self.cs.get_node(p)
 

	
 
    def __len__(self):
 
        return len(self.current_paths)
 

	
 
    def __iter__(self):
 
        for p in self.current_paths:
 
            yield self.cs.get_node(p)
 

	
 

	
 
class AddedFileNodesGenerator(NodeGeneratorBase):
 
    """
 
    Class holding Added files for current changeset
 
    """
 
    pass
 

	
 

	
 
class ChangedFileNodesGenerator(NodeGeneratorBase):
 
    """
 
    Class holding Changed files for current changeset
 
    """
 
    pass
 

	
 

	
 
class RemovedFileNodesGenerator(NodeGeneratorBase):
 
    """
 
    Class holding removed files for current changeset
 
    """
 
    def __iter__(self):
 
        for p in self.current_paths:
 
            yield RemovedFileNode(path=p)
 

	
 
    def __getslice__(self, i, j):
 
        for p in self.current_paths[i:j]:
 
            yield RemovedFileNode(path=p)
 

	
 

	
 
class Node(object):
 
    """
 
    Simplest class representing file or directory on repository.  SCM backends
 
    should use ``FileNode`` and ``DirNode`` subclasses rather than ``Node``
 
    directly.
 

	
 
    Node's ``path`` cannot start with slash as we operate on *relative* paths
 
    only. Moreover, every single node is identified by the ``path`` attribute,
 
    so it cannot end with slash, too. Otherwise, path could lead to mistakes.
 
    """
 

	
 
    def __init__(self, path, kind):
 
        if path.startswith('/'):
 
            raise NodeError("Cannot initialize Node objects with slash at "
 
                "the beginning as only relative paths are supported")
 
        self.path = path.rstrip('/')
 
        if path == '' and kind != NodeKind.DIR:
 
            raise NodeError("Only DirNode and its subclasses may be "
 
                            "initialized with empty path")
 
        self.kind = kind
 
        #self.dirs, self.files = [], []
 
        if self.is_root() and not self.is_dir():
 
            raise NodeError("Root node cannot be FILE kind")
 

	
 
    @LazyProperty
 
    def parent(self):
 
        parent_path = self.get_parent_path()
 
        if parent_path:
 
            if self.changeset:
 
                return self.changeset.get_node(parent_path)
 
            return DirNode(parent_path)
 
        return None
 

	
 
    @LazyProperty
 
    def unicode_path(self):
 
        return safe_unicode(self.path)
 

	
 
    @LazyProperty
 
    def name(self):
 
        """
 
        Returns name of the node so if its path
 
        then only last part is returned.
 
        """
 
        return safe_unicode(self.path.rstrip('/').split('/')[-1])
 

	
 
    def _get_kind(self):
 
        return self._kind
 

	
 
    def _set_kind(self, kind):
 
        if hasattr(self, '_kind'):
 
            raise NodeError("Cannot change node's kind")
 
        else:
 
            self._kind = kind
 
            # Post setter check (path's trailing slash)
 
            if self.path.endswith('/'):
 
                raise NodeError("Node's path cannot end with slash")
 

	
 
    kind = property(_get_kind, _set_kind)
 

	
 
    def __cmp__(self, other):
 
        """
 
        Comparator using name of the node, needed for quick list sorting.
 
        """
 
        kind_cmp = cmp(self.kind, other.kind)
 
        if kind_cmp:
 
            return kind_cmp
 
        return cmp(self.name, other.name)
 

	
 
    def __eq__(self, other):
 
        for attr in ['name', 'path', 'kind']:
 
            if getattr(self, attr) != getattr(other, attr):
 
                return False
 
        if self.is_file():
 
            if self.content != other.content:
 
                return False
 
        else:
 
            # For DirNode's check without entering each dir
 
            self_nodes_paths = list(sorted(n.path for n in self.nodes))
 
            other_nodes_paths = list(sorted(n.path for n in self.nodes))
 
            if self_nodes_paths != other_nodes_paths:
 
                return False
 
        return True
 

	
 
    def __nq__(self, other):
 
        return not self.__eq__(other)
 

	
 
    def __repr__(self):
 
        return '<%s %r>' % (self.__class__.__name__, self.path)
 

	
 
    def __str__(self):
 
        return self.__repr__()
 

	
 
    def __unicode__(self):
 
        return self.name
 

	
 
    def get_parent_path(self):
 
        """
 
        Returns node's parent path or empty string if node is root.
 
        """
 
        if self.is_root():
 
            return ''
 
        return posixpath.dirname(self.path.rstrip('/')) + '/'
 

	
 
    def is_file(self):
 
        """
 
        Returns ``True`` if node's kind is ``NodeKind.FILE``, ``False``
 
        otherwise.
 
        """
 
        return self.kind == NodeKind.FILE
 

	
 
    def is_dir(self):
 
        """
 
        Returns ``True`` if node's kind is ``NodeKind.DIR``, ``False``
 
        otherwise.
 
        """
 
        return self.kind == NodeKind.DIR
 

	
 
    def is_root(self):
 
        """
 
        Returns ``True`` if node is a root node and ``False`` otherwise.
 
        """
 
        return self.kind == NodeKind.DIR and self.path == ''
 

	
 
    def is_submodule(self):
 
        """
 
        Returns ``True`` if node's kind is ``NodeKind.SUBMODULE``, ``False``
 
        otherwise.
 
        """
 
        return self.kind == NodeKind.SUBMODULE
 

	
 
    @LazyProperty
 
    def added(self):
 
        return self.state is NodeState.ADDED
 

	
 
    @LazyProperty
 
    def changed(self):
 
        return self.state is NodeState.CHANGED
 

	
 
    @LazyProperty
 
    def not_changed(self):
 
        return self.state is NodeState.NOT_CHANGED
 

	
 
    @LazyProperty
 
    def removed(self):
 
        return self.state is NodeState.REMOVED
 

	
 

	
 
class FileNode(Node):
 
    """
 
    Class representing file nodes.
 

	
 
    :attribute: path: path to the node, relative to repostiory's root
 
    :attribute: content: if given arbitrary sets content of the file
 
    :attribute: changeset: if given, first time content is accessed, callback
 
    :attribute: mode: octal stat mode for a node. Default is 0100644.
 
    """
 

	
 
    def __init__(self, path, content=None, changeset=None, mode=None):
 
        """
 
        Only one of ``content`` and ``changeset`` may be given. Passing both
 
        would raise ``NodeError`` exception.
 

	
 
        :param path: relative path to the node
 
        :param content: content may be passed to constructor
 
        :param changeset: if given, will use it to lazily fetch content
 
        :param mode: octal representation of ST_MODE (i.e. 0100644)
 
        """
 

	
 
        if content and changeset:
 
            raise NodeError("Cannot use both content and changeset")
 
        super(FileNode, self).__init__(path, kind=NodeKind.FILE)
 
        self.changeset = changeset
 
        self._content = content
 
        self._mode = mode or 0100644
 

	
 
    @LazyProperty
 
    def mode(self):
 
        """
 
        Returns lazily mode of the FileNode. If ``changeset`` is not set, would
 
        use value given at initialization or 0100644 (default).
 
        """
 
        if self.changeset:
 
            mode = self.changeset.get_file_mode(self.path)
 
        else:
 
            mode = self._mode
 
        return mode
 

	
 
    @property
 
    def content(self):
 
        """
 
        Returns lazily content of the FileNode. If possible, would try to
 
        decode content from UTF-8.
 
        """
 
        if self.changeset:
 
            content = self.changeset.get_file_content(self.path)
 
        else:
 
            content = self._content
 

	
 
        if bool(content and '\0' in content):
 
            return content
 
        return safe_unicode(content)
 

	
 
    @LazyProperty
 
    def size(self):
 
        if self.changeset:
 
            return self.changeset.get_file_size(self.path)
 
        raise NodeError("Cannot retrieve size of the file without related "
 
            "changeset attribute")
 

	
 
    @LazyProperty
 
    def message(self):
 
        if self.changeset:
 
            return self.last_changeset.message
 
        raise NodeError("Cannot retrieve message of the file without related "
 
            "changeset attribute")
 

	
 
    @LazyProperty
 
    def last_changeset(self):
 
        if self.changeset:
 
            return self.changeset.get_file_changeset(self.path)
 
        raise NodeError("Cannot retrieve last changeset of the file without "
 
            "related changeset attribute")
 

	
 
    def get_mimetype(self):
 
        """
 
        Mimetype is calculated based on the file's content. If ``_mimetype``
 
@@ -464,96 +478,134 @@ class DirNode(Node):
 
    def content(self):
 
        raise NodeError("%s represents a dir and has no ``content`` attribute"
 
            % self)
 

	
 
    @LazyProperty
 
    def nodes(self):
 
        if self.changeset:
 
            nodes = self.changeset.get_nodes(self.path)
 
        else:
 
            nodes = self._nodes
 
        self._nodes_dict = dict((node.path, node) for node in nodes)
 
        return sorted(nodes)
 

	
 
    @LazyProperty
 
    def files(self):
 
        return sorted((node for node in self.nodes if node.is_file()))
 

	
 
    @LazyProperty
 
    def dirs(self):
 
        return sorted((node for node in self.nodes if node.is_dir()))
 

	
 
    def __iter__(self):
 
        for node in self.nodes:
 
            yield node
 

	
 
    def get_node(self, path):
 
        """
 
        Returns node from within this particular ``DirNode``, so it is now
 
        allowed to fetch, i.e. node located at 'docs/api/index.rst' from node
 
        'docs'. In order to access deeper nodes one must fetch nodes between
 
        them first - this would work::
 

	
 
           docs = root.get_node('docs')
 
           docs.get_node('api').get_node('index.rst')
 

	
 
        :param: path - relative to the current node
 

	
 
        .. note::
 
           To access lazily (as in example above) node have to be initialized
 
           with related changeset object - without it node is out of
 
           context and may know nothing about anything else than nearest
 
           (located at same level) nodes.
 
        """
 
        try:
 
            path = path.rstrip('/')
 
            if path == '':
 
                raise NodeError("Cannot retrieve node without path")
 
            self.nodes  # access nodes first in order to set _nodes_dict
 
            paths = path.split('/')
 
            if len(paths) == 1:
 
                if not self.is_root():
 
                    path = '/'.join((self.path, paths[0]))
 
                else:
 
                    path = paths[0]
 
                return self._nodes_dict[path]
 
            elif len(paths) > 1:
 
                if self.changeset is None:
 
                    raise NodeError("Cannot access deeper "
 
                                    "nodes without changeset")
 
                else:
 
                    path1, path2 = paths[0], '/'.join(paths[1:])
 
                    return self.get_node(path1).get_node(path2)
 
            else:
 
                raise KeyError
 
        except KeyError:
 
            raise NodeError("Node does not exist at %s" % path)
 

	
 
    @LazyProperty
 
    def state(self):
 
        raise NodeError("Cannot access state of DirNode")
 

	
 
    @LazyProperty
 
    def size(self):
 
        size = 0
 
        for root, dirs, files in self.changeset.walk(self.path):
 
            for f in files:
 
                size += f.size
 

	
 
        return size
 

	
 
    def __repr__(self):
 
        return '<%s %r @ %s>' % (self.__class__.__name__, self.path,
 
                                 self.changeset.short_id)
 

	
 

	
 
class RootNode(DirNode):
 
    """
 
    DirNode being the root node of the repository.
 
    """
 

	
 
    def __init__(self, nodes=(), changeset=None):
 
        super(RootNode, self).__init__(path='', nodes=nodes,
 
            changeset=changeset)
 

	
 
    def __repr__(self):
 
        return '<%s>' % self.__class__.__name__
 

	
 

	
 
class SubModuleNode(Node):
 
    """
 
    represents a SubModule of Git or SubRepo of Mercurial
 
    """
 
    is_binary = False
 
    size = 0
 

	
 
    def __init__(self, name, url=None, changeset=None, alias=None):
 
        self.path = name
 
        self.kind = NodeKind.SUBMODULE
 
        self.alias = alias
 
        # we have to use emptyChangeset here since this can point to svn/git/hg
 
        # submodules we cannot get from repository
 
        self.changeset = EmptyChangeset(str(changeset), alias=alias)
 
        self.url = url or self._extract_submodule_url()
 

	
 
    def __repr__(self):
 
        return '<%s %r @ %s>' % (self.__class__.__name__, self.path,
 
                                 self.changeset.short_id)
 

	
 
    def _extract_submodule_url(self):
 
        if self.alias == 'git':
 
            #TODO: find a way to parse gits submodule file and extract the
 
            # linking URL
 
            return self.path
 
        if self.alias == 'hg':
 
            return self.path
 

	
 
    @LazyProperty
 
    def name(self):
 
        """
 
        Returns name of the node so if its path
 
        then only last part is returned.
 
        """
 
        org = safe_unicode(self.path.rstrip('/').split('/')[-1])
 
        return u'%s @ %s' % (org, self.changeset.short_id)
rhodecode/lib/vcs/utils/hgcompat.py
Show inline comments
 
"""Mercurial libs compatibility
 
"""
 
Mercurial libs compatibility
 
"""
 

	
 
"""
 
from mercurial import archival, merge as hg_merge, patch, ui
 
from mercurial.commands import clone, nullid, pull
 
from mercurial.context import memctx, memfilectx
 
from mercurial.error import RepoError, RepoLookupError, Abort
 
from mercurial.hgweb.common import get_contact
 
from mercurial.localrepo import localrepository
 
from mercurial.match import match
 
from mercurial.mdiff import diffopts
 
from mercurial.node import hex
 
from mercurial.encoding import tolocal
rhodecode/model/comment.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
"""
 
    rhodecode.model.comment
 
    ~~~~~~~~~~~~~~~~~~~~~~~
 

	
 
    comments model for RhodeCode
 

	
 
    :created_on: Nov 11, 2011
 
    :author: marcink
 
    :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
 
    :license: GPLv3, see COPYING for more details.
 
"""
 
# This program is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# This program is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 

	
 
import logging
 
import traceback
 

	
 
from pylons.i18n.translation import _
 
from sqlalchemy.util.compat import defaultdict
 

	
 
from rhodecode.lib.utils2 import extract_mentioned_users
 
from rhodecode.lib.utils2 import extract_mentioned_users, safe_unicode
 
from rhodecode.lib import helpers as h
 
from rhodecode.model import BaseModel
 
from rhodecode.model.db import ChangesetComment, User, Repository, Notification
 
from rhodecode.model.notification import NotificationModel
 

	
 
log = logging.getLogger(__name__)
 

	
 

	
 
class ChangesetCommentsModel(BaseModel):
 

	
 
    def __get_changeset_comment(self, changeset_comment):
 
        return self._get_instance(ChangesetComment, changeset_comment)
 

	
 
    def _extract_mentions(self, s):
 
        user_objects = []
 
        for username in extract_mentioned_users(s):
 
            user_obj = User.get_by_username(username, case_insensitive=True)
 
            if user_obj:
 
                user_objects.append(user_obj)
 
        return user_objects
 

	
 
    def create(self, text, repo_id, user_id, revision, f_path=None,
 
               line_no=None):
 
        """
 
        Creates new comment for changeset
 

	
 
        :param text:
 
        :param repo_id:
 
        :param user_id:
 
        :param revision:
 
        :param f_path:
 
        :param line_no:
 
        """
 

	
 
        if text:
 
            repo = Repository.get(repo_id)
 
            cs = repo.scm_instance.get_changeset(revision)
 
            desc = cs.message
 
            desc = "%s - %s" % (cs.short_id, h.shorter(cs.message, 256))
 
            author_email = cs.author_email
 
            comment = ChangesetComment()
 
            comment.repo = repo
 
            comment.user_id = user_id
 
            comment.revision = revision
 
            comment.text = text
 
            comment.f_path = f_path
 
            comment.line_no = line_no
 

	
 
            self.sa.add(comment)
 
            self.sa.flush()
 
            # make notification
 
            line = ''
 
            if line_no:
 
                line = _('on line %s') % line_no
 
            subj = h.link_to('Re commit: %(commit_desc)s %(line)s' % \
 
            subj = safe_unicode(
 
                h.link_to('Re commit: %(commit_desc)s %(line)s' % \
 
                                    {'commit_desc': desc, 'line': line},
 
                             h.url('changeset_home', repo_name=repo.repo_name,
 
                                   revision=revision,
 
                                   anchor='comment-%s' % comment.comment_id,
 
                                   qualified=True,
 
                                   )
 
                             )
 
            )
 

	
 
            body = text
 

	
 
            # get the current participants of this changeset
 
            recipients = ChangesetComment.get_users(revision=revision)
 

	
 
            # add changeset author if it's in rhodecode system
 
            recipients += [User.get_by_email(author_email)]
 

	
 
            NotificationModel().create(
 
              created_by=user_id, subject=subj, body=body,
 
              recipients=recipients, type_=Notification.TYPE_CHANGESET_COMMENT
 
            )
 

	
 
            mention_recipients = set(self._extract_mentions(body))\
 
                                    .difference(recipients)
 
            if mention_recipients:
 
                subj = _('[Mention]') + ' ' + subj
 
                NotificationModel().create(
 
                    created_by=user_id, subject=subj, body=body,
 
                    recipients=mention_recipients,
 
                    type_=Notification.TYPE_CHANGESET_COMMENT
 
                )
 

	
 
            return comment
 

	
 
    def delete(self, comment):
 
        """
 
        Deletes given comment
 

	
 
        :param comment_id:
 
        """
 
        comment = self.__get_changeset_comment(comment)
 
        self.sa.delete(comment)
 

	
 
        return comment
 

	
 
    def get_comments(self, repo_id, revision):
 
        return ChangesetComment.query()\
 
                .filter(ChangesetComment.repo_id == repo_id)\
 
                .filter(ChangesetComment.revision == revision)\
 
                .filter(ChangesetComment.line_no == None)\
 
                .filter(ChangesetComment.f_path == None).all()
 

	
 
    def get_inline_comments(self, repo_id, revision):
 
        comments = self.sa.query(ChangesetComment)\
 
            .filter(ChangesetComment.repo_id == repo_id)\
 
            .filter(ChangesetComment.revision == revision)\
 
            .filter(ChangesetComment.line_no != None)\
 
            .filter(ChangesetComment.f_path != None).all()
 
            .filter(ChangesetComment.f_path != None)\
 
            .order_by(ChangesetComment.comment_id.asc())\
 
            .all()
 

	
 
        paths = defaultdict(lambda: defaultdict(list))
 

	
 
        for co in comments:
 
            paths[co.f_path][co.line_no].append(co)
 
        return paths.items()
rhodecode/model/db.py
Show inline comments
 
@@ -552,193 +552,193 @@ class Repository(Base, BaseModel):
 
    @property
 
    def just_name(self):
 
        return self.repo_name.split(Repository.url_sep())[-1]
 

	
 
    @property
 
    def groups_with_parents(self):
 
        groups = []
 
        if self.group is None:
 
            return groups
 

	
 
        cur_gr = self.group
 
        groups.insert(0, cur_gr)
 
        while 1:
 
            gr = getattr(cur_gr, 'parent_group', None)
 
            cur_gr = cur_gr.parent_group
 
            if gr is None:
 
                break
 
            groups.insert(0, gr)
 

	
 
        return groups
 

	
 
    @property
 
    def groups_and_repo(self):
 
        return self.groups_with_parents, self.just_name
 

	
 
    @LazyProperty
 
    def repo_path(self):
 
        """
 
        Returns base full path for that repository means where it actually
 
        exists on a filesystem
 
        """
 
        q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
 
                                              Repository.url_sep())
 
        q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
 
        return q.one().ui_value
 

	
 
    @property
 
    def repo_full_path(self):
 
        p = [self.repo_path]
 
        # we need to split the name by / since this is how we store the
 
        # names in the database, but that eventually needs to be converted
 
        # into a valid system path
 
        p += self.repo_name.split(Repository.url_sep())
 
        return os.path.join(*p)
 

	
 
    def get_new_name(self, repo_name):
 
        """
 
        returns new full repository name based on assigned group and new new
 

	
 
        :param group_name:
 
        """
 
        path_prefix = self.group.full_path_splitted if self.group else []
 
        return Repository.url_sep().join(path_prefix + [repo_name])
 

	
 
    @property
 
    def _ui(self):
 
        """
 
        Creates an db based ui object for this repository
 
        """
 
        from mercurial import ui
 
        from mercurial import config
 
        baseui = ui.ui()
 

	
 
        #clean the baseui object
 
        baseui._ocfg = config.config()
 
        baseui._ucfg = config.config()
 
        baseui._tcfg = config.config()
 

	
 
        ret = RhodeCodeUi.query()\
 
            .options(FromCache("sql_cache_short", "repository_repo_ui")).all()
 

	
 
        hg_ui = ret
 
        for ui_ in hg_ui:
 
            if ui_.ui_active:
 
                log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
 
                          ui_.ui_key, ui_.ui_value)
 
                baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
 

	
 
        return baseui
 

	
 
    @classmethod
 
    def is_valid(cls, repo_name):
 
        """
 
        returns True if given repo name is a valid filesystem repository
 

	
 
        :param cls:
 
        :param repo_name:
 
        """
 
        from rhodecode.lib.utils import is_valid_repo
 

	
 
        return is_valid_repo(repo_name, cls.base_path())
 

	
 
    #==========================================================================
 
    # SCM PROPERTIES
 
    #==========================================================================
 

	
 
    def get_changeset(self, rev):
 
    def get_changeset(self, rev=None):
 
        return get_changeset_safe(self.scm_instance, rev)
 

	
 
    @property
 
    def tip(self):
 
        return self.get_changeset('tip')
 

	
 
    @property
 
    def author(self):
 
        return self.tip.author
 

	
 
    @property
 
    def last_change(self):
 
        return self.scm_instance.last_change
 

	
 
    def comments(self, revisions=None):
 
        """
 
        Returns comments for this repository grouped by revisions
 

	
 
        :param revisions: filter query by revisions only
 
        """
 
        cmts = ChangesetComment.query()\
 
            .filter(ChangesetComment.repo == self)
 
        if revisions:
 
            cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
 
        grouped = defaultdict(list)
 
        for cmt in cmts.all():
 
            grouped[cmt.revision].append(cmt)
 
        return grouped
 

	
 
    #==========================================================================
 
    # SCM CACHE INSTANCE
 
    #==========================================================================
 

	
 
    @property
 
    def invalidate(self):
 
        return CacheInvalidation.invalidate(self.repo_name)
 

	
 
    def set_invalidate(self):
 
        """
 
        set a cache for invalidation for this instance
 
        """
 
        CacheInvalidation.set_invalidate(self.repo_name)
 

	
 
    @LazyProperty
 
    def scm_instance(self):
 
        return self.__get_instance()
 

	
 
    @property
 
    def scm_instance_cached(self):
 
        @cache_region('long_term')
 
        def _c(repo_name):
 
            return self.__get_instance()
 
        rn = self.repo_name
 
        log.debug('Getting cached instance of repo')
 
        inv = self.invalidate
 
        if inv is not None:
 
            region_invalidate(_c, None, rn)
 
            # update our cache
 
            CacheInvalidation.set_valid(inv.cache_key)
 
        return _c(rn)
 

	
 
    def __get_instance(self):
 
        repo_full_path = self.repo_full_path
 
        try:
 
            alias = get_scm(repo_full_path)[0]
 
            log.debug('Creating instance of %s repository' % alias)
 
            backend = get_backend(alias)
 
        except VCSError:
 
            log.error(traceback.format_exc())
 
            log.error('Perhaps this repository is in db and not in '
 
                      'filesystem run rescan repositories with '
 
                      '"destroy old data " option from admin panel')
 
            return
 

	
 
        if alias == 'hg':
 

	
 
            repo = backend(safe_str(repo_full_path), create=False,
 
                           baseui=self._ui)
 
            # skip hidden web repository
 
            if repo._get_hidden():
 
                return
 
        else:
 
            repo = backend(repo_full_path, create=False)
 

	
 
        return repo
 

	
 

	
 
class RepoGroup(Base, BaseModel):
 
    __tablename__ = 'groups'
 
    __table_args__ = (
 
        UniqueConstraint('group_name', 'group_parent_id'),
 
        CheckConstraint('group_id != group_parent_id'),
 
        {'extend_existing': True, 'mysql_engine':'InnoDB',
 
         'mysql_charset': 'utf8'},
 
    )
 
    __mapper_args__ = {'order_by': 'group_name'}
 
@@ -1140,153 +1140,154 @@ class CacheInvalidation(Base, BaseModel)
 
            return inv
 

	
 
    @classmethod
 
    def set_invalidate(cls, key):
 
        """
 
        Mark this Cache key for invalidation
 

	
 
        :param key:
 
        """
 

	
 
        key, _prefix, _org_key = cls._get_key(key)
 
        inv_objs = Session.query(cls).filter(cls.cache_args == _org_key).all()
 
        log.debug('marking %s key[s] %s for invalidation' % (len(inv_objs),
 
                                                             _org_key))
 
        try:
 
            for inv_obj in inv_objs:
 
                if inv_obj:
 
                    inv_obj.cache_active = False
 

	
 
                Session.add(inv_obj)
 
            Session.commit()
 
        except Exception:
 
            log.error(traceback.format_exc())
 
            Session.rollback()
 

	
 
    @classmethod
 
    def set_valid(cls, key):
 
        """
 
        Mark this cache key as active and currently cached
 

	
 
        :param key:
 
        """
 
        inv_obj = cls.get_by_key(key)
 
        inv_obj.cache_active = True
 
        Session.add(inv_obj)
 
        Session.commit()
 

	
 

	
 
class ChangesetComment(Base, BaseModel):
 
    __tablename__ = 'changeset_comments'
 
    __table_args__ = (
 
        {'extend_existing': True, 'mysql_engine':'InnoDB',
 
         'mysql_charset': 'utf8'},
 
    )
 
    comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
 
    repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
 
    revision = Column('revision', String(40), nullable=False)
 
    line_no = Column('line_no', Unicode(10), nullable=True)
 
    f_path = Column('f_path', Unicode(1000), nullable=True)
 
    user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
 
    text = Column('text', Unicode(25000), nullable=False)
 
    modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
 

	
 
    author = relationship('User', lazy='joined')
 
    repo = relationship('Repository')
 

	
 
    @classmethod
 
    def get_users(cls, revision):
 
        """
 
        Returns user associated with this changesetComment. ie those
 
        who actually commented
 

	
 
        :param cls:
 
        :param revision:
 
        """
 
        return Session.query(User)\
 
                .filter(cls.revision == revision)\
 
                .join(ChangesetComment.author).all()
 

	
 

	
 
class Notification(Base, BaseModel):
 
    __tablename__ = 'notifications'
 
    __table_args__ = (
 
        {'extend_existing': True, 'mysql_engine':'InnoDB',
 
         'mysql_charset': 'utf8'},
 
    )
 

	
 
    TYPE_CHANGESET_COMMENT = u'cs_comment'
 
    TYPE_MESSAGE = u'message'
 
    TYPE_MENTION = u'mention'
 
    TYPE_REGISTRATION = u'registration'
 

	
 
    notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
 
    subject = Column('subject', Unicode(512), nullable=True)
 
    body = Column('body', Unicode(50000), nullable=True)
 
    created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
 
    created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
 
    type_ = Column('type', Unicode(256))
 

	
 
    created_by_user = relationship('User')
 
    notifications_to_users = relationship('UserNotification', lazy='joined',
 
                                          cascade="all, delete, delete-orphan")
 

	
 
    @property
 
    def recipients(self):
 
        return [x.user for x in UserNotification.query()\
 
                .filter(UserNotification.notification == self).all()]
 
                .filter(UserNotification.notification == self)\
 
                .order_by(UserNotification.user).all()]
 

	
 
    @classmethod
 
    def create(cls, created_by, subject, body, recipients, type_=None):
 
        if type_ is None:
 
            type_ = Notification.TYPE_MESSAGE
 

	
 
        notification = cls()
 
        notification.created_by_user = created_by
 
        notification.subject = subject
 
        notification.body = body
 
        notification.type_ = type_
 
        notification.created_on = datetime.datetime.now()
 

	
 
        for u in recipients:
 
            assoc = UserNotification()
 
            assoc.notification = notification
 
            u.notifications.append(assoc)
 
        Session.add(notification)
 
        return notification
 

	
 
    @property
 
    def description(self):
 
        from rhodecode.model.notification import NotificationModel
 
        return NotificationModel().make_description(self)
 

	
 

	
 
class UserNotification(Base, BaseModel):
 
    __tablename__ = 'user_to_notification'
 
    __table_args__ = (
 
        UniqueConstraint('user_id', 'notification_id'),
 
        {'extend_existing': True, 'mysql_engine':'InnoDB',
 
         'mysql_charset': 'utf8'}
 
    )
 
    user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
 
    notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
 
    read = Column('read', Boolean, default=False)
 
    sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
 

	
 
    user = relationship('User', lazy="joined")
 
    notification = relationship('Notification', lazy="joined",
 
                                order_by=lambda: Notification.created_on.desc(),)
 

	
 
    def mark_as_read(self):
 
        self.read = True
 
        Session.add(self)
 

	
 

	
 
class DbMigrateVersion(Base, BaseModel):
 
    __tablename__ = 'db_migrate_version'
 
    __table_args__ = (
 
        {'extend_existing': True, 'mysql_engine':'InnoDB',
 
         'mysql_charset': 'utf8'},
 
    )
 
    repository_id = Column('repository_id', String(250), primary_key=True)
 
    repository_path = Column('repository_path', Text)
 
    version = Column('version', Integer)
rhodecode/model/forms.py
Show inline comments
 
@@ -458,316 +458,316 @@ def ValidPerms(type_='repo'):
 
                    msg = self.message('perm_new_member_name',
 
                                         state=State_obj)
 
                    raise formencode.Invalid(
 
                        msg, value, state, error_dict={'perm_new_member_name': msg}
 
                    )
 
            return value
 
    return _ValidPerms
 

	
 

	
 
class ValidSettings(formencode.validators.FancyValidator):
 

	
 
    def to_python(self, value, state):
 
        # settings  form can't edit user
 
        if 'user' in value:
 
            del['value']['user']
 
        return value
 

	
 

	
 
class ValidPath(formencode.validators.FancyValidator):
 
    def to_python(self, value, state):
 

	
 
        if not os.path.isdir(value):
 
            msg = _('This is not a valid path')
 
            raise formencode.Invalid(msg, value, state,
 
                                     error_dict={'paths_root_path': msg})
 
        return value
 

	
 

	
 
def UniqSystemEmail(old_data):
 
    class _UniqSystemEmail(formencode.validators.FancyValidator):
 
        def to_python(self, value, state):
 
            value = value.lower()
 
            if (old_data.get('email') or '').lower() != value:
 
                user = User.get_by_email(value, case_insensitive=True)
 
                if user:
 
                    raise formencode.Invalid(
 
                        _("This e-mail address is already taken"), value, state
 
                    )
 
            return value
 

	
 
    return _UniqSystemEmail
 

	
 

	
 
class ValidSystemEmail(formencode.validators.FancyValidator):
 
    def to_python(self, value, state):
 
        value = value.lower()
 
        user = User.get_by_email(value, case_insensitive=True)
 
        if  user is None:
 
            raise formencode.Invalid(
 
                _("This e-mail address doesn't exist."), value, state
 
            )
 

	
 
        return value
 

	
 

	
 
class LdapLibValidator(formencode.validators.FancyValidator):
 

	
 
    def to_python(self, value, state):
 

	
 
        try:
 
            import ldap
 
        except ImportError:
 
            raise LdapImportError
 
        return value
 

	
 

	
 
class AttrLoginValidator(formencode.validators.FancyValidator):
 

	
 
    def to_python(self, value, state):
 

	
 
        if not value or not isinstance(value, (str, unicode)):
 
            raise formencode.Invalid(
 
                _("The LDAP Login attribute of the CN must be specified - "
 
                  "this is the name of the attribute that is equivalent "
 
                  "to 'username'"), value, state
 
            )
 

	
 
        return value
 

	
 

	
 
#==============================================================================
 
# FORMS
 
#==============================================================================
 
class LoginForm(formencode.Schema):
 
    allow_extra_fields = True
 
    filter_extra_fields = True
 
    username = UnicodeString(
 
        strip=True,
 
        min=1,
 
        not_empty=True,
 
        messages={
 
           'empty': _('Please enter a login'),
 
           'tooShort': _('Enter a value %(min)i characters long or more')}
 
    )
 

	
 
    password = UnicodeString(
 
        strip=True,
 
        strip=False,
 
        min=3,
 
        not_empty=True,
 
        messages={
 
            'empty': _('Please enter a password'),
 
            'tooShort': _('Enter %(min)i characters or more')}
 
    )
 

	
 
    remember = StringBoolean(if_missing=False)
 

	
 
    chained_validators = [ValidAuth]
 

	
 

	
 
def UserForm(edit=False, old_data={}):
 
    class _UserForm(formencode.Schema):
 
        allow_extra_fields = True
 
        filter_extra_fields = True
 
        username = All(UnicodeString(strip=True, min=1, not_empty=True),
 
                       ValidUsername(edit, old_data))
 
        if edit:
 
            new_password = All(UnicodeString(strip=True, min=6, not_empty=False))
 
            password_confirmation = All(UnicodeString(strip=True, min=6,
 
            new_password = All(UnicodeString(strip=False, min=6, not_empty=False))
 
            password_confirmation = All(UnicodeString(strip=False, min=6,
 
                                                      not_empty=False))
 
            admin = StringBoolean(if_missing=False)
 
        else:
 
            password = All(UnicodeString(strip=True, min=6, not_empty=True))
 
            password_confirmation = All(UnicodeString(strip=True, min=6,
 
            password = All(UnicodeString(strip=False, min=6, not_empty=True))
 
            password_confirmation = All(UnicodeString(strip=False, min=6,
 
                                                      not_empty=False))
 

	
 
        active = StringBoolean(if_missing=False)
 
        name = UnicodeString(strip=True, min=1, not_empty=False)
 
        lastname = UnicodeString(strip=True, min=1, not_empty=False)
 
        email = All(Email(not_empty=True), UniqSystemEmail(old_data))
 

	
 
        chained_validators = [ValidPasswordsMatch, ValidPassword]
 

	
 
    return _UserForm
 

	
 

	
 
def UsersGroupForm(edit=False, old_data={}, available_members=[]):
 
    class _UsersGroupForm(formencode.Schema):
 
        allow_extra_fields = True
 
        filter_extra_fields = True
 

	
 
        users_group_name = All(UnicodeString(strip=True, min=1, not_empty=True),
 
                       ValidUsersGroup(edit, old_data))
 

	
 
        users_group_active = StringBoolean(if_missing=False)
 

	
 
        if edit:
 
            users_group_members = OneOf(available_members, hideList=False,
 
                                        testValueList=True,
 
                                        if_missing=None, not_empty=False)
 

	
 
    return _UsersGroupForm
 

	
 

	
 
def ReposGroupForm(edit=False, old_data={}, available_groups=[]):
 
    class _ReposGroupForm(formencode.Schema):
 
        allow_extra_fields = True
 
        filter_extra_fields = False
 

	
 
        group_name = All(UnicodeString(strip=True, min=1, not_empty=True),
 
                               SlugifyName())
 
        group_description = UnicodeString(strip=True, min=1,
 
                                                not_empty=True)
 
        group_parent_id = OneOf(available_groups, hideList=False,
 
                                        testValueList=True,
 
                                        if_missing=None, not_empty=False)
 

	
 
        chained_validators = [ValidReposGroup(edit, old_data), ValidPerms('group')]
 

	
 
    return _ReposGroupForm
 

	
 

	
 
def RegisterForm(edit=False, old_data={}):
 
    class _RegisterForm(formencode.Schema):
 
        allow_extra_fields = True
 
        filter_extra_fields = True
 
        username = All(ValidUsername(edit, old_data),
 
                       UnicodeString(strip=True, min=1, not_empty=True))
 
        password = All(UnicodeString(strip=True, min=6, not_empty=True))
 
        password_confirmation = All(UnicodeString(strip=True, min=6, not_empty=True))
 
        password = All(UnicodeString(strip=False, min=6, not_empty=True))
 
        password_confirmation = All(UnicodeString(strip=False, min=6, not_empty=True))
 
        active = StringBoolean(if_missing=False)
 
        name = UnicodeString(strip=True, min=1, not_empty=False)
 
        lastname = UnicodeString(strip=True, min=1, not_empty=False)
 
        email = All(Email(not_empty=True), UniqSystemEmail(old_data))
 

	
 
        chained_validators = [ValidPasswordsMatch, ValidPassword]
 

	
 
    return _RegisterForm
 

	
 

	
 
def PasswordResetForm():
 
    class _PasswordResetForm(formencode.Schema):
 
        allow_extra_fields = True
 
        filter_extra_fields = True
 
        email = All(ValidSystemEmail(), Email(not_empty=True))
 
    return _PasswordResetForm
 

	
 

	
 
def RepoForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
 
             repo_groups=[]):
 
    class _RepoForm(formencode.Schema):
 
        allow_extra_fields = True
 
        filter_extra_fields = False
 
        repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
 
                        SlugifyName())
 
        clone_uri = All(UnicodeString(strip=True, min=1, not_empty=False))
 
        repo_group = OneOf(repo_groups, hideList=True)
 
        repo_type = OneOf(supported_backends)
 
        description = UnicodeString(strip=True, min=1, not_empty=True)
 
        private = StringBoolean(if_missing=False)
 
        enable_statistics = StringBoolean(if_missing=False)
 
        enable_downloads = StringBoolean(if_missing=False)
 

	
 
        if edit:
 
            #this is repo owner
 
            user = All(UnicodeString(not_empty=True), ValidRepoUser)
 

	
 
        chained_validators = [ValidCloneUri()(),
 
                              ValidRepoName(edit, old_data),
 
                              ValidPerms()]
 
    return _RepoForm
 

	
 

	
 
def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
 
                 repo_groups=[]):
 
    class _RepoForkForm(formencode.Schema):
 
        allow_extra_fields = True
 
        filter_extra_fields = False
 
        repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
 
                        SlugifyName())
 
        repo_group = OneOf(repo_groups, hideList=True)
 
        repo_type = All(ValidForkType(old_data), OneOf(supported_backends))
 
        description = UnicodeString(strip=True, min=1, not_empty=True)
 
        private = StringBoolean(if_missing=False)
 
        copy_permissions = StringBoolean(if_missing=False)
 
        update_after_clone = StringBoolean(if_missing=False)
 
        fork_parent_id = UnicodeString()
 
        chained_validators = [ValidForkName(edit, old_data)]
 

	
 
    return _RepoForkForm
 

	
 

	
 
def RepoSettingsForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
 
                     repo_groups=[]):
 
    class _RepoForm(formencode.Schema):
 
        allow_extra_fields = True
 
        filter_extra_fields = False
 
        repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
 
                        SlugifyName())
 
        description = UnicodeString(strip=True, min=1, not_empty=True)
 
        repo_group = OneOf(repo_groups, hideList=True)
 
        private = StringBoolean(if_missing=False)
 

	
 
        chained_validators = [ValidRepoName(edit, old_data), ValidPerms(),
 
                              ValidSettings]
 
    return _RepoForm
 

	
 

	
 
def ApplicationSettingsForm():
 
    class _ApplicationSettingsForm(formencode.Schema):
 
        allow_extra_fields = True
 
        filter_extra_fields = False
 
        rhodecode_title = UnicodeString(strip=True, min=1, not_empty=True)
 
        rhodecode_realm = UnicodeString(strip=True, min=1, not_empty=True)
 
        rhodecode_ga_code = UnicodeString(strip=True, min=1, not_empty=False)
 

	
 
    return _ApplicationSettingsForm
 

	
 

	
 
def ApplicationUiSettingsForm():
 
    class _ApplicationUiSettingsForm(formencode.Schema):
 
        allow_extra_fields = True
 
        filter_extra_fields = False
 
        web_push_ssl = OneOf(['true', 'false'], if_missing='false')
 
        paths_root_path = All(ValidPath(), UnicodeString(strip=True, min=1, not_empty=True))
 
        hooks_changegroup_update = OneOf(['True', 'False'], if_missing=False)
 
        hooks_changegroup_repo_size = OneOf(['True', 'False'], if_missing=False)
 
        hooks_pretxnchangegroup_push_logger = OneOf(['True', 'False'], if_missing=False)
 
        hooks_preoutgoing_pull_logger = OneOf(['True', 'False'], if_missing=False)
 

	
 
    return _ApplicationUiSettingsForm
 

	
 

	
 
def DefaultPermissionsForm(perms_choices, register_choices, create_choices):
 
    class _DefaultPermissionsForm(formencode.Schema):
 
        allow_extra_fields = True
 
        filter_extra_fields = True
 
        overwrite_default = StringBoolean(if_missing=False)
 
        anonymous = OneOf(['True', 'False'], if_missing=False)
 
        default_perm = OneOf(perms_choices)
 
        default_register = OneOf(register_choices)
 
        default_create = OneOf(create_choices)
 

	
 
    return _DefaultPermissionsForm
 

	
 

	
 
def LdapSettingsForm(tls_reqcert_choices, search_scope_choices, tls_kind_choices):
 
    class _LdapSettingsForm(formencode.Schema):
 
        allow_extra_fields = True
 
        filter_extra_fields = True
 
        pre_validators = [LdapLibValidator]
 
        #pre_validators = [LdapLibValidator]
 
        ldap_active = StringBoolean(if_missing=False)
 
        ldap_host = UnicodeString(strip=True,)
 
        ldap_port = Number(strip=True,)
 
        ldap_tls_kind = OneOf(tls_kind_choices)
 
        ldap_tls_reqcert = OneOf(tls_reqcert_choices)
 
        ldap_dn_user = UnicodeString(strip=True,)
 
        ldap_dn_pass = UnicodeString(strip=True,)
 
        ldap_base_dn = UnicodeString(strip=True,)
 
        ldap_filter = UnicodeString(strip=True,)
 
        ldap_search_scope = OneOf(search_scope_choices)
 
        ldap_attr_login = All(AttrLoginValidator, UnicodeString(strip=True,))
 
        ldap_attr_firstname = UnicodeString(strip=True,)
 
        ldap_attr_lastname = UnicodeString(strip=True,)
 
        ldap_attr_email = UnicodeString(strip=True,)
 

	
 
    return _LdapSettingsForm
rhodecode/model/repo.py
Show inline comments
 
@@ -193,198 +193,198 @@ class RepoModel(BaseModel):
 
                    cur_repo.group = RepoGroup.get(v)
 

	
 
                else:
 
                    setattr(cur_repo, k, v)
 

	
 
            new_name = cur_repo.get_new_name(form_data['repo_name'])
 
            cur_repo.repo_name = new_name
 

	
 
            self.sa.add(cur_repo)
 

	
 
            if repo_name != new_name:
 
                # rename repository
 
                self.__rename_repo(old=repo_name, new=new_name)
 

	
 
            return cur_repo
 
        except:
 
            log.error(traceback.format_exc())
 
            raise
 

	
 
    def create(self, form_data, cur_user, just_db=False, fork=False):
 
        from rhodecode.model.scm import ScmModel
 

	
 
        try:
 
            if fork:
 
                fork_parent_id = form_data['fork_parent_id']
 

	
 
            # repo name is just a name of repository
 
            # while repo_name_full is a full qualified name that is combined
 
            # with name and path of group
 
            repo_name = form_data['repo_name']
 
            repo_name_full = form_data['repo_name_full']
 

	
 
            new_repo = Repository()
 
            new_repo.enable_statistics = False
 

	
 
            for k, v in form_data.items():
 
                if k == 'repo_name':
 
                    v = repo_name_full
 
                if k == 'repo_group':
 
                    k = 'group_id'
 
                if k == 'description':
 
                    v = v or repo_name
 

	
 
                setattr(new_repo, k, v)
 

	
 
            if fork:
 
                parent_repo = Repository.get(fork_parent_id)
 
                new_repo.fork = parent_repo
 

	
 
            new_repo.user_id = cur_user.user_id
 
            self.sa.add(new_repo)
 

	
 
            def _create_default_perms():
 
                # create default permission
 
                repo_to_perm = UserRepoToPerm()
 
                default = 'repository.read'
 
                for p in User.get_by_username('default').user_perms:
 
                    if p.permission.permission_name.startswith('repository.'):
 
                        default = p.permission.permission_name
 
                        break
 

	
 
                default_perm = 'repository.none' if form_data['private'] else default
 

	
 
                repo_to_perm.permission_id = self.sa.query(Permission)\
 
                        .filter(Permission.permission_name == default_perm)\
 
                        .one().permission_id
 

	
 
                repo_to_perm.repository = new_repo
 
                repo_to_perm.user_id = User.get_by_username('default').user_id
 

	
 
                self.sa.add(repo_to_perm)
 

	
 
            if fork:
 
                if form_data.get('copy_permissions'):
 
                    repo = Repository.get(fork_parent_id)
 
                    user_perms = UserRepoToPerm.query()\
 
                        .filter(UserRepoToPerm.repository == repo).all()
 
                    group_perms = UsersGroupRepoToPerm.query()\
 
                        .filter(UsersGroupRepoToPerm.repository == repo).all()
 

	
 
                    for perm in user_perms:
 
                        UserRepoToPerm.create(perm.user, new_repo,
 
                                              perm.permission)
 

	
 
                    for perm in group_perms:
 
                        UsersGroupRepoToPerm.create(perm.users_group, new_repo,
 
                                                    perm.permission)
 
                else:
 
                    _create_default_perms()
 
            else:
 
                _create_default_perms()
 

	
 
            if not just_db:
 
                self.__create_repo(repo_name, form_data['repo_type'],
 
                                   form_data['repo_group'],
 
                                   form_data['clone_uri'])
 
                log_create_repository(new_repo.get_dict(),
 
                                      created_by=cur_user.username)
 

	
 
            # now automatically start following this repository as owner
 
            ScmModel(self.sa).toggle_following_repo(new_repo.repo_id,
 
                                                    cur_user.user_id)
 
            log_create_repository(new_repo.get_dict(),
 
                                  created_by=cur_user.username)
 
            return new_repo
 
        except:
 
            log.error(traceback.format_exc())
 
            raise
 

	
 
    def create_fork(self, form_data, cur_user):
 
        """
 
        Simple wrapper into executing celery task for fork creation
 

	
 
        :param form_data:
 
        :param cur_user:
 
        """
 
        from rhodecode.lib.celerylib import tasks, run_task
 
        run_task(tasks.create_repo_fork, form_data, cur_user)
 

	
 
    def delete(self, repo):
 
        repo = self.__get_repo(repo)
 
        try:
 
            self.sa.delete(repo)
 
            self.__delete_repo(repo)
 
        except:
 
            log.error(traceback.format_exc())
 
            raise
 

	
 
    def grant_user_permission(self, repo, user, perm):
 
        """
 
        Grant permission for user on given repository, or update existing one
 
        if found
 

	
 
        :param repo: Instance of Repository, repository_id, or repository name
 
        :param user: Instance of User, user_id or username
 
        :param perm: Instance of Permission, or permission_name
 
        """
 
        user = self.__get_user(user)
 
        repo = self.__get_repo(repo)
 
        permission = self.__get_perm(perm)
 

	
 
        # check if we have that permission already
 
        obj = self.sa.query(UserRepoToPerm)\
 
            .filter(UserRepoToPerm.user == user)\
 
            .filter(UserRepoToPerm.repository == repo)\
 
            .scalar()
 
        if obj is None:
 
            # create new !
 
            obj = UserRepoToPerm()
 
        obj.repository = repo
 
        obj.user = user
 
        obj.permission = permission
 
        self.sa.add(obj)
 

	
 
    def revoke_user_permission(self, repo, user):
 
        """
 
        Revoke permission for user on given repository
 

	
 
        :param repo: Instance of Repository, repository_id, or repository name
 
        :param user: Instance of User, user_id or username
 
        """
 

	
 
        user = self.__get_user(user)
 
        repo = self.__get_repo(repo)
 

	
 
        obj = self.sa.query(UserRepoToPerm)\
 
            .filter(UserRepoToPerm.repository == repo)\
 
            .filter(UserRepoToPerm.user == user)\
 
            .one()
 
        self.sa.delete(obj)
 

	
 
    def grant_users_group_permission(self, repo, group_name, perm):
 
        """
 
        Grant permission for users group on given repository, or update
 
        existing one if found
 

	
 
        :param repo: Instance of Repository, repository_id, or repository name
 
        :param group_name: Instance of UserGroup, users_group_id,
 
            or users group name
 
        :param perm: Instance of Permission, or permission_name
 
        """
 
        repo = self.__get_repo(repo)
 
        group_name = self.__get_users_group(group_name)
 
        permission = self.__get_perm(perm)
 

	
 
        # check if we have that permission already
 
        obj = self.sa.query(UsersGroupRepoToPerm)\
 
            .filter(UsersGroupRepoToPerm.users_group == group_name)\
 
            .filter(UsersGroupRepoToPerm.repository == repo)\
 
            .scalar()
 

	
 
        if obj is None:
 
            # create new
 
            obj = UsersGroupRepoToPerm()
 

	
 
        obj.repository = repo
 
        obj.users_group = group_name
 
        obj.permission = permission
 
        self.sa.add(obj)
 

	
rhodecode/model/scm.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
"""
 
    rhodecode.model.scm
 
    ~~~~~~~~~~~~~~~~~~~
 

	
 
    Scm model for RhodeCode
 

	
 
    :created_on: Apr 9, 2010
 
    :author: marcink
 
    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
 
    :license: GPLv3, see COPYING for more details.
 
"""
 
# This program is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# This program is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
import os
 
import time
 
import traceback
 
import logging
 
import cStringIO
 

	
 
from rhodecode.lib.vcs import get_backend
 
from rhodecode.lib.vcs.exceptions import RepositoryError
 
from rhodecode.lib.vcs.utils.lazy import LazyProperty
 
from rhodecode.lib.vcs.nodes import FileNode
 

	
 
from rhodecode import BACKENDS
 
from rhodecode.lib import helpers as h
 
from rhodecode.lib.utils2 import safe_str
 
from rhodecode.lib.utils2 import safe_str, safe_unicode
 
from rhodecode.lib.auth import HasRepoPermissionAny, HasReposGroupPermissionAny
 
from rhodecode.lib.utils import get_repos as get_filesystem_repos, make_ui, \
 
    action_logger, EmptyChangeset, REMOVED_REPO_PAT
 
from rhodecode.model import BaseModel
 
from rhodecode.model.db import Repository, RhodeCodeUi, CacheInvalidation, \
 
    UserFollowing, UserLog, User, RepoGroup
 

	
 
log = logging.getLogger(__name__)
 

	
 

	
 
class UserTemp(object):
 
    def __init__(self, user_id):
 
        self.user_id = user_id
 

	
 
    def __repr__(self):
 
        return "<%s('id:%s')>" % (self.__class__.__name__, self.user_id)
 

	
 

	
 
class RepoTemp(object):
 
    def __init__(self, repo_id):
 
        self.repo_id = repo_id
 

	
 
    def __repr__(self):
 
        return "<%s('id:%s')>" % (self.__class__.__name__, self.repo_id)
 

	
 

	
 
class CachedRepoList(object):
 

	
 
    def __init__(self, db_repo_list, repos_path, order_by=None):
 
        self.db_repo_list = db_repo_list
 
        self.repos_path = repos_path
 
        self.order_by = order_by
 
        self.reversed = (order_by or '').startswith('-')
 

	
 
    def __len__(self):
 
        return len(self.db_repo_list)
 

	
 
    def __repr__(self):
 
        return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
 

	
 
    def __iter__(self):
 
        for dbr in self.db_repo_list:
 
            scmr = dbr.scm_instance_cached
 
            # check permission at this level
 
            if not HasRepoPermissionAny(
 
                'repository.read', 'repository.write', 'repository.admin'
 
            )(dbr.repo_name, 'get repo check'):
 
                continue
 

	
 
            if scmr is None:
 
                log.error(
 
                    '%s this repository is present in database but it '
 
                    'cannot be created as an scm instance' % dbr.repo_name
 
                )
 
                continue
 

	
 
            last_change = scmr.last_change
 
            tip = h.get_changeset_safe(scmr, 'tip')
 

	
 
            tmp_d = {}
 
            tmp_d['name'] = dbr.repo_name
 
            tmp_d['name_sort'] = tmp_d['name'].lower()
 
            tmp_d['description'] = dbr.description
 
            tmp_d['description_sort'] = tmp_d['description']
 
            tmp_d['last_change'] = last_change
 
            tmp_d['last_change_sort'] = time.mktime(last_change.timetuple())
 
            tmp_d['tip'] = tip.raw_id
 
            tmp_d['tip_sort'] = tip.revision
 
            tmp_d['rev'] = tip.revision
 
            tmp_d['contact'] = dbr.user.full_contact
 
            tmp_d['contact_sort'] = tmp_d['contact']
 
            tmp_d['owner_sort'] = tmp_d['contact']
 
            tmp_d['repo_archives'] = list(scmr._get_archives())
 
            tmp_d['last_msg'] = tip.message
 
            tmp_d['author'] = tip.author
 
            tmp_d['dbrepo'] = dbr.get_dict()
 
            tmp_d['dbrepo_fork'] = dbr.fork.get_dict() if dbr.fork else {}
 
            yield tmp_d
 

	
 

	
 
class GroupList(object):
 

	
 
    def __init__(self, db_repo_group_list):
 
        self.db_repo_group_list = db_repo_group_list
 

	
 
    def __len__(self):
 
        return len(self.db_repo_group_list)
 

	
 
    def __repr__(self):
 
        return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
 

	
 
    def __iter__(self):
 
        for dbgr in self.db_repo_group_list:
 
            # check permission at this level
 
            if not HasReposGroupPermissionAny(
 
                'group.read', 'group.write', 'group.admin'
 
@@ -250,210 +250,217 @@ class ScmModel(BaseModel):
 
            .filter(UserFollowing.user_id == user_id).scalar()
 

	
 
        if f is not None:
 
            try:
 
                self.sa.delete(f)
 
                action_logger(UserTemp(user_id),
 
                              'stopped_following_repo',
 
                              RepoTemp(follow_repo_id))
 
                return
 
            except:
 
                log.error(traceback.format_exc())
 
                raise
 

	
 
        try:
 
            f = UserFollowing()
 
            f.user_id = user_id
 
            f.follows_repo_id = follow_repo_id
 
            self.sa.add(f)
 

	
 
            action_logger(UserTemp(user_id),
 
                          'started_following_repo',
 
                          RepoTemp(follow_repo_id))
 
        except:
 
            log.error(traceback.format_exc())
 
            raise
 

	
 
    def toggle_following_user(self, follow_user_id, user_id):
 
        f = self.sa.query(UserFollowing)\
 
            .filter(UserFollowing.follows_user_id == follow_user_id)\
 
            .filter(UserFollowing.user_id == user_id).scalar()
 

	
 
        if f is not None:
 
            try:
 
                self.sa.delete(f)
 
                return
 
            except:
 
                log.error(traceback.format_exc())
 
                raise
 

	
 
        try:
 
            f = UserFollowing()
 
            f.user_id = user_id
 
            f.follows_user_id = follow_user_id
 
            self.sa.add(f)
 
        except:
 
            log.error(traceback.format_exc())
 
            raise
 

	
 
    def is_following_repo(self, repo_name, user_id, cache=False):
 
        r = self.sa.query(Repository)\
 
            .filter(Repository.repo_name == repo_name).scalar()
 

	
 
        f = self.sa.query(UserFollowing)\
 
            .filter(UserFollowing.follows_repository == r)\
 
            .filter(UserFollowing.user_id == user_id).scalar()
 

	
 
        return f is not None
 

	
 
    def is_following_user(self, username, user_id, cache=False):
 
        u = User.get_by_username(username)
 

	
 
        f = self.sa.query(UserFollowing)\
 
            .filter(UserFollowing.follows_user == u)\
 
            .filter(UserFollowing.user_id == user_id).scalar()
 

	
 
        return f is not None
 

	
 
    def get_followers(self, repo_id):
 
        if not isinstance(repo_id, int):
 
            repo_id = getattr(Repository.get_by_repo_name(repo_id), 'repo_id')
 

	
 
        return self.sa.query(UserFollowing)\
 
                .filter(UserFollowing.follows_repo_id == repo_id).count()
 

	
 
    def get_forks(self, repo_id):
 
        if not isinstance(repo_id, int):
 
            repo_id = getattr(Repository.get_by_repo_name(repo_id), 'repo_id')
 

	
 
        return self.sa.query(Repository)\
 
                .filter(Repository.fork_id == repo_id).count()
 

	
 
    def mark_as_fork(self, repo, fork, user):
 
        repo = self.__get_repo(repo)
 
        fork = self.__get_repo(fork)
 
        repo.fork = fork
 
        self.sa.add(repo)
 
        return repo
 

	
 
    def pull_changes(self, repo_name, username):
 
        dbrepo = Repository.get_by_repo_name(repo_name)
 
        clone_uri = dbrepo.clone_uri
 
        if not clone_uri:
 
            raise Exception("This repository doesn't have a clone uri")
 

	
 
        repo = dbrepo.scm_instance
 
        try:
 
            extras = {'ip': '',
 
            extras = {
 
                'ip': '',
 
                      'username': username,
 
                      'action': 'push_remote',
 
                      'repository': repo_name}
 
                'repository': repo_name,
 
                'scm': repo.alias,
 
            }
 

	
 
            #inject ui extra param to log this action via push logger
 
            for k, v in extras.items():
 
                repo._repo.ui.setconfig('rhodecode_extras', k, v)
 

	
 
            repo.pull(clone_uri)
 
            self.mark_for_invalidation(repo_name)
 
        except:
 
            log.error(traceback.format_exc())
 
            raise
 

	
 
    def commit_change(self, repo, repo_name, cs, user, author, message,
 
                      content, f_path):
 

	
 
        if repo.alias == 'hg':
 
            from rhodecode.lib.vcs.backends.hg import MercurialInMemoryChangeset as IMC
 
            from rhodecode.lib.vcs.backends.hg import \
 
                MercurialInMemoryChangeset as IMC
 
        elif repo.alias == 'git':
 
            from rhodecode.lib.vcs.backends.git import GitInMemoryChangeset as IMC
 
            from rhodecode.lib.vcs.backends.git import \
 
                GitInMemoryChangeset as IMC
 

	
 
        # decoding here will force that we have proper encoded values
 
        # in any other case this will throw exceptions and deny commit
 
        content = safe_str(content)
 
        message = safe_str(message)
 
        path = safe_str(f_path)
 
        author = safe_str(author)
 
        # message and author needs to be unicode
 
        # proper backend should then translate that into required type
 
        message = safe_unicode(message)
 
        author = safe_unicode(author)
 
        m = IMC(repo)
 
        m.change(FileNode(path, content))
 
        tip = m.commit(message=message,
 
                 author=author,
 
                 parents=[cs], branch=cs.branch)
 

	
 
        new_cs = tip.short_id
 
        action = 'push_local:%s' % new_cs
 

	
 
        action_logger(user, action, repo_name)
 

	
 
        self.mark_for_invalidation(repo_name)
 

	
 
    def create_node(self, repo, repo_name, cs, user, author, message, content,
 
                      f_path):
 
        if repo.alias == 'hg':
 
            from rhodecode.lib.vcs.backends.hg import MercurialInMemoryChangeset as IMC
 
        elif repo.alias == 'git':
 
            from rhodecode.lib.vcs.backends.git import GitInMemoryChangeset as IMC
 
        # decoding here will force that we have proper encoded values
 
        # in any other case this will throw exceptions and deny commit
 

	
 
        if isinstance(content, (basestring,)):
 
            content = safe_str(content)
 
        elif isinstance(content, (file, cStringIO.OutputType,)):
 
            content = content.read()
 
        else:
 
            raise Exception('Content is of unrecognized type %s' % (
 
                type(content)
 
            ))
 

	
 
        message = safe_str(message)
 
        message = safe_unicode(message)
 
        author = safe_unicode(author)
 
        path = safe_str(f_path)
 
        author = safe_str(author)
 
        m = IMC(repo)
 

	
 
        if isinstance(cs, EmptyChangeset):
 
            # Emptychangeset means we we're editing empty repository
 
            # EmptyChangeset means we we're editing empty repository
 
            parents = None
 
        else:
 
            parents = [cs]
 

	
 
        m.add(FileNode(path, content=content))
 
        tip = m.commit(message=message,
 
                 author=author,
 
                 parents=parents, branch=cs.branch)
 
        new_cs = tip.short_id
 
        action = 'push_local:%s' % new_cs
 

	
 
        action_logger(user, action, repo_name)
 

	
 
        self.mark_for_invalidation(repo_name)
 

	
 
    def get_nodes(self, repo_name, revision, root_path='/', flat=True):
 
        """
 
        recursive walk in root dir and return a set of all path in that dir
 
        based on repository walk function
 

	
 
        :param repo_name: name of repository
 
        :param revision: revision for which to list nodes
 
        :param root_path: root path to list
 
        :param flat: return as a list, if False returns a dict with decription
 

	
 
        """
 
        _files = list()
 
        _dirs = list()
 
        try:
 
            _repo = self.__get_repo(repo_name)
 
            changeset = _repo.scm_instance.get_changeset(revision)
 
            root_path = root_path.lstrip('/')
 
            for topnode, dirs, files in changeset.walk(root_path):
 
                for f in files:
 
                    _files.append(f.path if flat else {"name": f.path,
 
                                                       "type": "file"})
 
                for d in dirs:
 
                    _dirs.append(d.path if flat else {"name": d.path,
 
                                                      "type": "dir"})
 
        except RepositoryError:
 
            log.debug(traceback.format_exc())
 
            raise
 

	
 
        return _dirs, _files
 

	
 
    def get_unread_journal(self):
 
        return self.sa.query(UserLog).count()
rhodecode/model/user.py
Show inline comments
 
@@ -132,458 +132,459 @@ class UserModel(BaseModel):
 
        else:
 
            log.debug('updating user %s' % username)
 
            new_user = user
 

	
 
        try:
 
            new_user.username = username
 
            new_user.admin = admin
 
            new_user.password = get_crypt_password(password)
 
            new_user.api_key = generate_api_key(username)
 
            new_user.email = email
 
            new_user.active = active
 
            new_user.ldap_dn = safe_unicode(ldap_dn) if ldap_dn else None
 
            new_user.name = name
 
            new_user.lastname = lastname
 
            self.sa.add(new_user)
 
            return new_user
 
        except (DatabaseError,):
 
            log.error(traceback.format_exc())
 
            raise
 

	
 
    def create_for_container_auth(self, username, attrs):
 
        """
 
        Creates the given user if it's not already in the database
 

	
 
        :param username:
 
        :param attrs:
 
        """
 
        if self.get_by_username(username, case_insensitive=True) is None:
 

	
 
            # autogenerate email for container account without one
 
            generate_email = lambda usr: '%s@container_auth.account' % usr
 

	
 
            try:
 
                new_user = User()
 
                new_user.username = username
 
                new_user.password = None
 
                new_user.api_key = generate_api_key(username)
 
                new_user.email = attrs['email']
 
                new_user.active = attrs.get('active', True)
 
                new_user.name = attrs['name'] or generate_email(username)
 
                new_user.lastname = attrs['lastname']
 

	
 
                self.sa.add(new_user)
 
                return new_user
 
            except (DatabaseError,):
 
                log.error(traceback.format_exc())
 
                self.sa.rollback()
 
                raise
 
        log.debug('User %s already exists. Skipping creation of account'
 
                  ' for container auth.', username)
 
        return None
 

	
 
    def create_ldap(self, username, password, user_dn, attrs):
 
        """
 
        Checks if user is in database, if not creates this user marked
 
        as ldap user
 

	
 
        :param username:
 
        :param password:
 
        :param user_dn:
 
        :param attrs:
 
        """
 
        from rhodecode.lib.auth import get_crypt_password
 
        log.debug('Checking for such ldap account in RhodeCode database')
 
        if self.get_by_username(username, case_insensitive=True) is None:
 

	
 
            # autogenerate email for ldap account without one
 
            generate_email = lambda usr: '%s@ldap.account' % usr
 

	
 
            try:
 
                new_user = User()
 
                username = username.lower()
 
                # add ldap account always lowercase
 
                new_user.username = username
 
                new_user.password = get_crypt_password(password)
 
                new_user.api_key = generate_api_key(username)
 
                new_user.email = attrs['email'] or generate_email(username)
 
                new_user.active = attrs.get('active', True)
 
                new_user.ldap_dn = safe_unicode(user_dn)
 
                new_user.name = attrs['name']
 
                new_user.lastname = attrs['lastname']
 

	
 
                self.sa.add(new_user)
 
                return new_user
 
            except (DatabaseError,):
 
                log.error(traceback.format_exc())
 
                self.sa.rollback()
 
                raise
 
        log.debug('this %s user exists skipping creation of ldap account',
 
                  username)
 
        return None
 

	
 
    def create_registration(self, form_data):
 
        from rhodecode.model.notification import NotificationModel
 

	
 
        try:
 
            new_user = User()
 
            for k, v in form_data.items():
 
                if k != 'admin':
 
                    setattr(new_user, k, v)
 
            form_data['admin'] = False
 
            new_user = self.create(form_data)
 

	
 
            self.sa.add(new_user)
 
            self.sa.flush()
 

	
 
            # notification to admins
 
            subject = _('new user registration')
 
            body = ('New user registration\n'
 
                    '---------------------\n'
 
                    '- Username: %s\n'
 
                    '- Full Name: %s\n'
 
                    '- Email: %s\n')
 
            body = body % (new_user.username, new_user.full_name,
 
                           new_user.email)
 
            edit_url = url('edit_user', id=new_user.user_id, qualified=True)
 
            kw = {'registered_user_url': edit_url}
 
            NotificationModel().create(created_by=new_user, subject=subject,
 
                                       body=body, recipients=None,
 
                                       type_=Notification.TYPE_REGISTRATION,
 
                                       email_kwargs=kw)
 

	
 
        except:
 
            log.error(traceback.format_exc())
 
            raise
 

	
 
    def update(self, user_id, form_data):
 
        try:
 
            user = self.get(user_id, cache=False)
 
            if user.username == 'default':
 
                raise DefaultUserException(
 
                                _("You can't Edit this user since it's"
 
                                  " crucial for entire application"))
 

	
 
            for k, v in form_data.items():
 
                if k == 'new_password' and v != '':
 
                    user.password = v
 
                    user.api_key = generate_api_key(user.username)
 
                else:
 
                    setattr(user, k, v)
 

	
 
            self.sa.add(user)
 
        except:
 
            log.error(traceback.format_exc())
 
            raise
 

	
 
    def update_my_account(self, user_id, form_data):
 
        try:
 
            user = self.get(user_id, cache=False)
 
            if user.username == 'default':
 
                raise DefaultUserException(
 
                                _("You can't Edit this user since it's"
 
                                  " crucial for entire application"))
 
            for k, v in form_data.items():
 
                if k == 'new_password' and v != '':
 
                    user.password = v
 
                    user.api_key = generate_api_key(user.username)
 
                else:
 
                    if k not in ['admin', 'active']:
 
                        setattr(user, k, v)
 

	
 
            self.sa.add(user)
 
        except:
 
            log.error(traceback.format_exc())
 
            raise
 

	
 
    def delete(self, user):
 
        user = self.__get_user(user)
 

	
 
        try:
 
            if user.username == 'default':
 
                raise DefaultUserException(
 
                    _(u"You can't remove this user since it's"
 
                      " crucial for entire application")
 
                )
 
            if user.repositories:
 
                repos = [x.repo_name for x in user.repositories]
 
                raise UserOwnsReposException(
 
                    _(u'user "%s" still owns %s repositories and cannot be '
 
                      'removed. Switch owners or remove those repositories. %s')
 
                    % (user.username, len(repos), ', '.join(repos))
 
                )
 
            self.sa.delete(user)
 
        except:
 
            log.error(traceback.format_exc())
 
            raise
 

	
 
    def reset_password_link(self, data):
 
        from rhodecode.lib.celerylib import tasks, run_task
 
        run_task(tasks.send_password_link, data['email'])
 

	
 
    def reset_password(self, data):
 
        from rhodecode.lib.celerylib import tasks, run_task
 
        run_task(tasks.reset_user_password, data['email'])
 

	
 
    def fill_data(self, auth_user, user_id=None, api_key=None):
 
        """
 
        Fetches auth_user by user_id,or api_key if present.
 
        Fills auth_user attributes with those taken from database.
 
        Additionally set's is_authenitated if lookup fails
 
        present in database
 

	
 
        :param auth_user: instance of user to set attributes
 
        :param user_id: user id to fetch by
 
        :param api_key: api key to fetch by
 
        """
 
        if user_id is None and api_key is None:
 
            raise Exception('You need to pass user_id or api_key')
 

	
 
        try:
 
            if api_key:
 
                dbuser = self.get_by_api_key(api_key)
 
            else:
 
                dbuser = self.get(user_id)
 

	
 
            if dbuser is not None and dbuser.active:
 
                log.debug('filling %s data' % dbuser)
 
                for k, v in dbuser.get_dict().items():
 
                    setattr(auth_user, k, v)
 
            else:
 
                return False
 

	
 
        except:
 
            log.error(traceback.format_exc())
 
            auth_user.is_authenticated = False
 
            return False
 

	
 
        return True
 

	
 
    def fill_perms(self, user):
 
        """
 
        Fills user permission attribute with permissions taken from database
 
        works for permissions given for repositories, and for permissions that
 
        are granted to groups
 

	
 
        :param user: user instance to fill his perms
 
        """
 
        RK = 'repositories'
 
        GK = 'repositories_groups'
 
        GLOBAL = 'global'
 
        user.permissions[RK] = {}
 
        user.permissions[GK] = {}
 
        user.permissions[GLOBAL] = set()
 

	
 
        #======================================================================
 
        # fetch default permissions
 
        #======================================================================
 
        default_user = User.get_by_username('default', cache=True)
 
        default_user_id = default_user.user_id
 

	
 
        default_repo_perms = Permission.get_default_perms(default_user_id)
 
        default_repo_groups_perms = Permission.get_default_group_perms(default_user_id)
 

	
 
        if user.is_admin:
 
            #==================================================================
 
            # admin user have all default rights for repositories
 
            # and groups set to admin
 
            #==================================================================
 
            user.permissions[GLOBAL].add('hg.admin')
 

	
 
            # repositories
 
            for perm in default_repo_perms:
 
                r_k = perm.UserRepoToPerm.repository.repo_name
 
                p = 'repository.admin'
 
                user.permissions[RK][r_k] = p
 

	
 
            # repositories groups
 
            for perm in default_repo_groups_perms:
 
                rg_k = perm.UserRepoGroupToPerm.group.group_name
 
                p = 'group.admin'
 
                user.permissions[GK][rg_k] = p
 
            return user
 

	
 
        else:
 
            #==================================================================
 
            # set default permissions first for repositories and groups
 
            #==================================================================
 
            uid = user.user_id
 

	
 
            # default global permissions
 
            default_global_perms = self.sa.query(UserToPerm)\
 
                .filter(UserToPerm.user_id == default_user_id)
 

	
 
            for perm in default_global_perms:
 
                user.permissions[GLOBAL].add(perm.permission.permission_name)
 

	
 
            # defaults for repositories, taken from default user
 
            for perm in default_repo_perms:
 
                r_k = perm.UserRepoToPerm.repository.repo_name
 
                if perm.Repository.private and not (perm.Repository.user_id == uid):
 
                    # disable defaults for private repos,
 
                    p = 'repository.none'
 
                elif perm.Repository.user_id == uid:
 
                    # set admin if owner
 
                    p = 'repository.admin'
 
                else:
 
                    p = perm.Permission.permission_name
 

	
 
                user.permissions[RK][r_k] = p
 

	
 
            # defaults for repositories groups taken from default user permission
 
            # on given group
 
            for perm in default_repo_groups_perms:
 
                rg_k = perm.UserRepoGroupToPerm.group.group_name
 
                p = perm.Permission.permission_name
 
                user.permissions[GK][rg_k] = p
 

	
 
            #==================================================================
 
            # overwrite defaults with user permissions if any found
 
            #==================================================================
 

	
 
            # user global permissions
 
            user_perms = self.sa.query(UserToPerm)\
 
                    .options(joinedload(UserToPerm.permission))\
 
                    .filter(UserToPerm.user_id == uid).all()
 

	
 
            for perm in user_perms:
 
                user.permissions[GLOBAL].add(perm.permission.permission_name)
 

	
 
            # user explicit permissions for repositories
 
            user_repo_perms = \
 
             self.sa.query(UserRepoToPerm, Permission, Repository)\
 
             .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
 
             .join((Permission, UserRepoToPerm.permission_id == Permission.permission_id))\
 
             .filter(UserRepoToPerm.user_id == uid)\
 
             .all()
 

	
 
            for perm in user_repo_perms:
 
                # set admin if owner
 
                r_k = perm.UserRepoToPerm.repository.repo_name
 
                if perm.Repository.user_id == uid:
 
                    p = 'repository.admin'
 
                else:
 
                    p = perm.Permission.permission_name
 
                user.permissions[RK][r_k] = p
 

	
 
        # USER GROUP
 
            #==================================================================
 
            # check if user is part of user groups for this repository and
 
            # fill in (or replace with higher) permissions
 
            #==================================================================
 

	
 
            # users group global
 
            user_perms_from_users_groups = self.sa.query(UsersGroupToPerm)\
 
                .options(joinedload(UsersGroupToPerm.permission))\
 
                .join((UsersGroupMember, UsersGroupToPerm.users_group_id ==
 
                       UsersGroupMember.users_group_id))\
 
                .filter(UsersGroupMember.user_id == uid).all()
 

	
 
            for perm in user_perms_from_users_groups:
 
                user.permissions[GLOBAL].add(perm.permission.permission_name)
 

	
 
            # users group for repositories permissions
 
            user_repo_perms_from_users_groups = \
 
             self.sa.query(UsersGroupRepoToPerm, Permission, Repository,)\
 
             .join((Repository, UsersGroupRepoToPerm.repository_id == Repository.repo_id))\
 
             .join((Permission, UsersGroupRepoToPerm.permission_id == Permission.permission_id))\
 
             .join((UsersGroupMember, UsersGroupRepoToPerm.users_group_id == UsersGroupMember.users_group_id))\
 
             .filter(UsersGroupMember.user_id == uid)\
 
             .all()
 

	
 
            for perm in user_repo_perms_from_users_groups:
 
                r_k = perm.UsersGroupRepoToPerm.repository.repo_name
 
                p = perm.Permission.permission_name
 
                cur_perm = user.permissions[RK][r_k]
 
                # overwrite permission only if it's greater than permission
 
                # given from other sources
 
                if PERM_WEIGHTS[p] > PERM_WEIGHTS[cur_perm]:
 
                    user.permissions[RK][r_k] = p
 

	
 
        # REPO GROUP
 
            #==================================================================
 
            # get access for this user for repos group and override defaults
 
            #==================================================================
 

	
 
            # user explicit permissions for repository
 
            user_repo_groups_perms = \
 
             self.sa.query(UserRepoGroupToPerm, Permission, RepoGroup)\
 
             .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
 
             .join((Permission, UserRepoGroupToPerm.permission_id == Permission.permission_id))\
 
             .filter(UserRepoGroupToPerm.user_id == uid)\
 
             .all()
 

	
 
            for perm in user_repo_groups_perms:
 
                rg_k = perm.UserRepoGroupToPerm.group.group_name
 
                p = perm.Permission.permission_name
 
                cur_perm = user.permissions[GK][rg_k]
 
                if PERM_WEIGHTS[p] > PERM_WEIGHTS[cur_perm]:
 
                    user.permissions[GK][rg_k] = p
 

	
 
        # REPO GROUP + USER GROUP
 
            #==================================================================
 
            # check if user is part of user groups for this repo group and
 
            # fill in (or replace with higher) permissions
 
            #==================================================================
 

	
 
            # users group for repositories permissions
 
            user_repo_group_perms_from_users_groups = \
 
             self.sa.query(UsersGroupRepoGroupToPerm, Permission, RepoGroup)\
 
             .join((RepoGroup, UsersGroupRepoGroupToPerm.group_id == RepoGroup.group_id))\
 
             .join((Permission, UsersGroupRepoGroupToPerm.permission_id == Permission.permission_id))\
 
             .join((UsersGroupMember, UsersGroupRepoGroupToPerm.users_group_id == UsersGroupMember.users_group_id))\
 
             .filter(UsersGroupMember.user_id == uid)\
 
             .all()
 

	
 
            for perm in user_repo_group_perms_from_users_groups:
 
                g_k = perm.UsersGroupRepoGroupToPerm.group.group_name
 
                print perm, g_k
 
                p = perm.Permission.permission_name
 
                cur_perm = user.permissions[GK][g_k]
 
                # overwrite permission only if it's greater than permission
 
                # given from other sources
 
                if PERM_WEIGHTS[p] > PERM_WEIGHTS[cur_perm]:
 
                    user.permissions[GK][g_k] = p
 

	
 
        return user
 

	
 
    def has_perm(self, user, perm):
 
        if not isinstance(perm, Permission):
 
            raise Exception('perm needs to be an instance of Permission class '
 
                            'got %s instead' % type(perm))
 

	
 
        user = self.__get_user(user)
 

	
 
        return UserToPerm.query().filter(UserToPerm.user == user)\
 
            .filter(UserToPerm.permission == perm).scalar() is not None
 

	
 
    def grant_perm(self, user, perm):
 
        """
 
        Grant user global permissions
 

	
 
        :param user:
 
        :param perm:
 
        """
 
        user = self.__get_user(user)
 
        perm = self.__get_perm(perm)
 
        # if this permission is already granted skip it
 
        _perm = UserToPerm.query()\
 
            .filter(UserToPerm.user == user)\
 
            .filter(UserToPerm.permission == perm)\
 
            .scalar()
 
        if _perm:
 
            return
 
        new = UserToPerm()
 
        new.user = user
 
        new.permission = perm
 
        self.sa.add(new)
 

	
 
    def revoke_perm(self, user, perm):
 
        """
 
        Revoke users global permissions
 

	
 
        :param user:
 
        :param perm:
 
        """
 
        user = self.__get_user(user)
 
        perm = self.__get_perm(perm)
 

	
 
        obj = UserToPerm.query()\
 
                .filter(UserToPerm.user == user)\
 
                .filter(UserToPerm.permission == perm)\
 
                .scalar()
 
        if obj:
 
            self.sa.delete(obj)
rhodecode/public/css/style.css
Show inline comments
 
@@ -2427,390 +2427,402 @@ h3.files_location {
 
	margin-top: 4px;
 
}
 
 
#graph_content .container .mid .message {
 
	white-space: pre-wrap;
 
}
 
 
#graph_content .container .mid .message a:hover{
 
	text-decoration: none;
 
}
 
#content #graph_content .message .revision-link,
 
#changeset_content .container .message .revision-link
 
 {
 
	color:#3F6F9F;
 
    font-weight: bold !important;
 
}
 
 
#content #graph_content .message .issue-tracker-link,
 
#changeset_content .container .message .issue-tracker-link{
 
    color:#3F6F9F;
 
    font-weight: bold !important;
 
}
 
 
.right .comments-container{
 
	padding-right: 5px;
 
	margin-top:1px;
 
	float:right;
 
	height:14px;
 
}
 
 
.right .comments-cnt{
 
    float: left;
 
    color: rgb(136, 136, 136); 
 
    padding-right: 2px; 
 
}
 
 
.right .changes{
 
	clear: both;
 
}
 
 
.right .changes .changed_total {
 
	display: block;
 
	float: right;
 
	text-align: center;
 
	min-width: 45px;
 
	cursor: pointer;
 
	color: #444444;
 
	background: #FEA;
 
	-webkit-border-radius: 0px 0px 0px 6px;
 
	-moz-border-radius: 0px 0px 0px 6px;
 
	border-radius: 0px 0px 0px 6px;
 
	padding: 1px;
 
}
 
 
.right .changes .added,.changed,.removed {
 
	display: block;
 
	padding: 1px;
 
	color: #444444;
 
	float: right;
 
	text-align: center;
 
	min-width: 15px;
 
}
 
 
.right .changes .added {
 
	background: #CFC;
 
}
 
 
.right .changes .changed {
 
	background: #FEA;
 
}
 
 
.right .changes .removed {
 
	background: #FAA;
 
}
 
 
.right .merge {
 
  padding: 1px 3px 1px 3px;
 
  background-color: #fca062;
 
  font-size: 10px;
 
  font-weight: bold;
 
  color: #ffffff;
 
  text-transform: uppercase;
 
  white-space: nowrap;
 
  -webkit-border-radius: 3px;
 
  -moz-border-radius: 3px;
 
  border-radius: 3px;
 
  margin-right: 2px;
 
}
 
 
.right .parent {
 
	color: #666666;
 
	clear:both;
 
}
 
.right .logtags{
 
	padding: 2px 2px 2px 2px;
 
}
 
.right .logtags .branchtag,.right .logtags .tagtag,.right .logtags .booktag{
 
    margin: 0px 2px;
 
}
 
 
.right .logtags .branchtag,.logtags .branchtag {
 
  padding: 1px 3px 1px 3px;
 
  background-color: #bfbfbf;
 
  font-size: 10px;
 
  font-weight: bold;
 
  color: #ffffff;
 
  text-transform: uppercase;
 
  white-space: nowrap;
 
  -webkit-border-radius: 3px;
 
  -moz-border-radius: 3px;
 
  border-radius: 3px;
 
}
 
.right .logtags .branchtag a:hover,.logtags .branchtag a{
 
	color: #ffffff;
 
}
 
.right .logtags .branchtag a:hover,.logtags .branchtag a:hover{
 
	text-decoration: none;
 
	color: #ffffff;
 
}
 
.right .logtags .tagtag,.logtags .tagtag {
 
  padding: 1px 3px 1px 3px;
 
  background-color: #62cffc;
 
  font-size: 10px;
 
  font-weight: bold;
 
  color: #ffffff;
 
  text-transform: uppercase;
 
  white-space: nowrap;
 
  -webkit-border-radius: 3px;
 
  -moz-border-radius: 3px;
 
  border-radius: 3px;
 
}
 
.right .logtags .tagtag a:hover,.logtags .tagtag a{
 
	color: #ffffff;
 
}
 
.right .logtags .tagtag a:hover,.logtags .tagtag a:hover{
 
    text-decoration: none;
 
    color: #ffffff;
 
}
 
.right .logbooks .bookbook,.logbooks .bookbook {
 
  padding: 1px 3px 2px;
 
.right .logbooks .bookbook,.logbooks .bookbook,.right .logtags .bookbook,.logtags .bookbook {
 
  padding: 1px 3px 1px 3px;
 
  background-color: #46A546;
 
  font-size: 9.75px;
 
  font-size: 10px;
 
  font-weight: bold;
 
  color: #ffffff;
 
  text-transform: uppercase;
 
  white-space: nowrap;
 
  -webkit-border-radius: 3px;
 
  -moz-border-radius: 3px;
 
  border-radius: 3px;
 
}
 
.right .logbooks .bookbook,.logbooks .bookbook a{
 
.right .logbooks .bookbook,.logbooks .bookbook a,.right .logtags .bookbook,.logtags .bookbook a{
 
	color: #ffffff;
 
}
 
.right .logbooks .bookbook,.logbooks .bookbook a:hover{
 
.right .logbooks .bookbook,.logbooks .bookbook a:hover,.right .logtags .bookbook,.logtags .bookbook a:hover{
 
    text-decoration: none;
 
    color: #ffffff;
 
}
 
div.browserblock {
 
	overflow: hidden;
 
	border: 1px solid #ccc;
 
	background: #f8f8f8;
 
	font-size: 100%;
 
	line-height: 125%;
 
	padding: 0;
 
    -webkit-border-radius: 6px 6px 0px 0px;
 
    -moz-border-radius: 6px 6px 0px 0px;
 
    border-radius: 6px 6px 0px 0px;	
 
}
 
 
div.browserblock .browser-header {
 
	background: #FFF;
 
	padding: 10px 0px 15px 0px;
 
	width: 100%;
 
}
 
 
div.browserblock .browser-nav {
 
	float: left
 
}
 
 
div.browserblock .browser-branch {
 
	float: left;
 
}
 
 
div.browserblock .browser-branch label {
 
	color: #4A4A4A;
 
	vertical-align: text-top;
 
}
 
 
div.browserblock .browser-header span {
 
	margin-left: 5px;
 
	font-weight: 700;
 
}
 
 
div.browserblock .browser-search {
 
	clear: both;
 
	padding: 8px 8px 0px 5px;
 
	height: 20px;
 
}
 
 
div.browserblock #node_filter_box {
 
	
 
}
 
 
div.browserblock .search_activate {
 
	float: left
 
}
 
 
div.browserblock .add_node {
 
	float: left;
 
	padding-left: 5px;
 
}
 
 
div.browserblock .search_activate a:hover,div.browserblock .add_node a:hover
 
	{
 
	text-decoration: none !important;
 
}
 
 
div.browserblock .browser-body {
 
	background: #EEE;
 
	border-top: 1px solid #CCC;
 
}
 
 
table.code-browser {
 
	border-collapse: collapse;
 
	width: 100%;
 
}
 
 
table.code-browser tr {
 
	margin: 3px;
 
}
 
 
table.code-browser thead th {
 
	background-color: #EEE;
 
	height: 20px;
 
	font-size: 1.1em;
 
	font-weight: 700;
 
	text-align: left;
 
	padding-left: 10px;
 
}
 
 
table.code-browser tbody td {
 
	padding-left: 10px;
 
	height: 20px;
 
}
 
 
table.code-browser .browser-file {
 
	background: url("../images/icons/document_16.png") no-repeat scroll 3px;
 
	height: 16px;
 
	padding-left: 20px;
 
	text-align: left;
 
}
 
.diffblock .changeset_header {
 
    height: 16px;
 
}
 
.diffblock .changeset_file {
 
	background: url("../images/icons/file.png") no-repeat scroll 3px;
 
	text-align: left;
 
	float: left;
 
	padding: 2px 0px 2px 22px;
 
}
 
.diffblock .diff-menu-wrapper{
 
	float: left;
 
}
 
 
.diffblock .diff-menu{
 
    position: absolute;
 
    background: none repeat scroll 0 0 #FFFFFF;
 
    border-color: #003367 #666666 #666666;
 
    border-right: 1px solid #666666;
 
    border-style: solid solid solid;
 
    border-width: 1px;
 
    box-shadow: 2px 8px 4px rgba(0, 0, 0, 0.2);
 
    margin-top:5px;
 
    margin-left:1px;
 
    
 
}
 
.diffblock .diff-actions {
 
    padding: 2px 0px 0px 2px;
 
    float: left;
 
}
 
.diffblock  .diff-menu ul li {
 
	padding: 0px 0px 0px 0px !important;
 
}
 
.diffblock  .diff-menu ul li a{
 
	display: block;
 
	padding: 3px 8px 3px 8px !important;
 
}
 
.diffblock  .diff-menu ul li a:hover{
 
    text-decoration: none;
 
    background-color: #EEEEEE;
 
}
 
table.code-browser .browser-dir {
 
	background: url("../images/icons/folder_16.png") no-repeat scroll 3px;
 
	height: 16px;
 
	padding-left: 20px;
 
	text-align: left;
 
}
 
 
table.code-browser .submodule-dir {
 
    background: url("../images/icons/disconnect.png") no-repeat scroll 3px;
 
    height: 16px;
 
    padding-left: 20px;
 
    text-align: left;
 
}
 
 
 
.box .search {
 
	clear: both;
 
	overflow: hidden;
 
	margin: 0;
 
	padding: 0 20px 10px;
 
}
 
 
.box .search div.search_path {
 
	background: none repeat scroll 0 0 #EEE;
 
	border: 1px solid #CCC;
 
	color: blue;
 
	margin-bottom: 10px;
 
	padding: 10px 0;
 
}
 
 
.box .search div.search_path div.link {
 
	font-weight: 700;
 
	margin-left: 25px;
 
}
 
 
.box .search div.search_path div.link a {
 
	color: #003367;
 
	cursor: pointer;
 
	text-decoration: none;
 
}
 
 
#path_unlock {
 
	color: red;
 
	font-size: 1.2em;
 
	padding-left: 4px;
 
}
 
 
.info_box span {
 
	margin-left: 3px;
 
	margin-right: 3px;
 
}
 
 
.info_box .rev {
 
	color: #003367;
 
	font-size: 1.6em;
 
	font-weight: bold;
 
	vertical-align: sub;
 
}
 
 
.info_box input#at_rev,.info_box input#size {
 
	background: #FFF;
 
	border-top: 1px solid #b3b3b3;
 
	border-left: 1px solid #b3b3b3;
 
	border-right: 1px solid #eaeaea;
 
	border-bottom: 1px solid #eaeaea;
 
	color: #000;
 
	font-size: 12px;
 
	margin: 0;
 
	padding: 1px 5px 1px;
 
}
 
 
.info_box input#view {
 
	text-align: center;
 
	padding: 4px 3px 2px 2px;
 
}
 
 
.yui-overlay,.yui-panel-container {
 
	visibility: hidden;
 
	position: absolute;
 
	z-index: 2;
 
}
 
 
.yui-tt {
 
	visibility: hidden;
 
	position: absolute;
 
	color: #666;
 
	background-color: #FFF;
 
	border: 2px solid #003367;
 
	font: 100% sans-serif;
 
	width: auto;
 
	opacity: 1px;
 
	padding: 8px;
 
	white-space: pre-wrap;
 
	-webkit-border-radius: 8px 8px 8px 8px;
 
	-khtml-border-radius: 8px 8px 8px 8px;
 
	-moz-border-radius: 8px 8px 8px 8px;
 
	border-radius: 8px 8px 8px 8px;
 
	box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
 
}
 
 
.ac {
 
	vertical-align: top;
 
}
 
 
.ac .yui-ac {
 
	position: inherit;
 
	font-size: 100%;
 
}
 
 
.ac .perm_ac {
 
	width: 20em;
 
@@ -3873,360 +3885,403 @@ div.rst-block  pre {
 
    -webkit-border-radius: 4px;
 
    -moz-border-radius: 4px;
 
    border-radius: 4px;    
 
}
 
 
.comments .comment .meta {
 
    background: #f8f8f8;
 
    padding: 4px;
 
    border-bottom: 1px solid #ddd;
 
}
 
 
.comments .comment .meta img {
 
    vertical-align: middle;
 
}
 
 
.comments .comment .meta .user {
 
    font-weight: bold;
 
}
 
 
.comments .comment .meta .date {
 
}
 
 
.comments .comment .text {
 
    background-color: #FAFAFA;
 
}
 
.comment .text div.rst-block p {
 
	margin: 0.5em 0px !important;
 
}
 
 
.comments .comments-number{
 
	padding:0px 0px 10px 0px;
 
	font-weight: bold;
 
	color: #666;
 
	font-size: 16px;
 
}
 
 
/** comment form **/
 
 
.comment-form .clearfix{
 
	background: #EEE;
 
    -webkit-border-radius: 4px;
 
    -moz-border-radius: 4px;
 
    border-radius: 4px;
 
    padding: 10px;
 
}
 
 
div.comment-form {
 
    margin-top: 20px;
 
}
 
 
.comment-form strong {
 
    display: block;
 
    margin-bottom: 15px;
 
}
 
 
.comment-form textarea {
 
    width: 100%;
 
    height: 100px;
 
    font-family: 'Monaco', 'Courier', 'Courier New', monospace;
 
}
 
 
form.comment-form {
 
    margin-top: 10px;
 
    margin-left: 10px;
 
}
 
 
.comment-form-submit {
 
    margin-top: 5px;
 
    margin-left: 525px;
 
}
 
 
.file-comments {
 
    display: none;
 
}
 
 
.comment-form .comment {
 
    margin-left: 10px;
 
}
 
 
.comment-form .comment-help{
 
    padding: 0px 0px 5px 0px;
 
    color: #666;
 
}
 
 
.comment-form .comment-button{
 
	padding-top:5px;
 
}
 
 
.add-another-button {
 
    margin-left: 10px;
 
    margin-top: 10px;
 
    margin-bottom: 10px;
 
}
 
 
.comment .buttons {
 
	float: right;
 
	padding:2px 2px 0px 0px;
 
}
 
 
 
.show-inline-comments{
 
	position: relative;
 
	top:1px
 
}
 
 
/** comment inline form **/
 
.comment-inline-form .overlay{
 
	display: none;
 
}
 
.comment-inline-form .overlay.submitting{
 
	display:block;
 
    background: none repeat scroll 0 0 white;
 
    font-size: 16px;
 
    opacity: 0.5;
 
    position: absolute;
 
    text-align: center;
 
    vertical-align: top;
 
 
}
 
.comment-inline-form .overlay.submitting .overlay-text{
 
	width:100%;
 
	margin-top:5%;
 
}
 
 
.comment-inline-form .clearfix{
 
    background: #EEE;
 
    -webkit-border-radius: 4px;
 
    -moz-border-radius: 4px;
 
    border-radius: 4px;
 
    padding: 5px;
 
}
 
 
div.comment-inline-form {
 
    margin-top: 5px;
 
    padding:2px 6px 8px 6px;
 
 
}
 
 
.comment-inline-form strong {
 
    display: block;
 
    margin-bottom: 15px;
 
}
 
 
.comment-inline-form textarea {
 
    width: 100%;
 
    height: 100px;
 
    font-family: 'Monaco', 'Courier', 'Courier New', monospace;
 
}
 
 
form.comment-inline-form {
 
    margin-top: 10px;
 
    margin-left: 10px;
 
}
 
 
.comment-inline-form-submit {
 
    margin-top: 5px;
 
    margin-left: 525px;
 
}
 
 
.file-comments {
 
    display: none;
 
}
 
 
.comment-inline-form .comment {
 
    margin-left: 10px;
 
}
 
 
.comment-inline-form .comment-help{
 
    padding: 0px 0px 2px 0px;
 
    color: #666666;
 
    font-size: 10px;
 
}
 
 
.comment-inline-form .comment-button{
 
    padding-top:5px;
 
}
 
 
/** comment inline **/
 
.inline-comments {
 
    padding:10px 20px;
 
}
 
 
.inline-comments div.rst-block  {
 
	clear:both;
 
	overflow:hidden;
 
	margin:0;
 
	padding:0 20px 0px;
 
}
 
.inline-comments .comment {
 
    border: 1px solid #ddd;
 
    -webkit-border-radius: 4px;
 
    -moz-border-radius: 4px;
 
    border-radius: 4px;
 
    margin: 3px 3px 5px 5px;
 
    background-color: #FAFAFA;
 
}
 
.inline-comments .add-comment {
 
	padding: 2px 4px 8px 5px;
 
}
 
 
.inline-comments .comment-wrapp{
 
	padding:1px;
 
}
 
.inline-comments .comment .meta {
 
    background: #f8f8f8;
 
    padding: 4px;
 
    border-bottom: 1px solid #ddd;
 
}
 
 
.inline-comments .comment .meta img {
 
    vertical-align: middle;
 
}
 
 
.inline-comments .comment .meta .user {
 
    font-weight: bold;
 
}
 
 
.inline-comments .comment .meta .date {
 
}
 
 
.inline-comments .comment .text {
 
    background-color: #FAFAFA;
 
}
 
 
.inline-comments .comments-number{
 
    padding:0px 0px 10px 0px;
 
    font-weight: bold;
 
    color: #666;
 
    font-size: 16px;
 
}
 
.inline-comments-button .add-comment{
 
	margin:10px 5px !important;
 
}
 
	margin:2px 0px 8px 5px !important
 
}
 
 
 
.notification-paginator{
 
    padding: 0px 0px 4px 16px;
 
    float: left;    	
 
}
 
 
.notifications{
 
    border-radius: 4px 4px 4px 4px;
 
    -webkit-border-radius: 4px;
 
    -moz-border-radius: 4px;    
 
    float: right;
 
    margin: 20px 0px 0px 0px;
 
    position: absolute;
 
    text-align: center;
 
    width: 26px;
 
    z-index: 1000;
 
}
 
.notifications a{
 
	color:#888 !important;
 
	display: block;
 
	font-size: 10px;
 
	background-color: #DEDEDE !important;
 
    border-radius: 2px !important;
 
    -webkit-border-radius: 2px !important;
 
    -moz-border-radius: 2px !important;  	
 
}
 
.notifications a:hover{
 
	text-decoration: none !important;
 
	background-color: #EEEFFF !important;
 
}
 
.notification-header{
 
	padding-top:6px;
 
}
 
.notification-header .desc{
 
	font-size: 16px;
 
    height: 24px;
 
    float: left
 
}
 
.notification-list .container.unread{
 
	
 
	background: none repeat scroll 0 0 rgba(255, 255, 180, 0.6);
 
}
 
.notification-header .gravatar{
 
	
 
    background: none repeat scroll 0 0 transparent;
 
    padding: 0px 0px 0px 8px;	
 
}
 
.notification-header .desc.unread{
 
    font-weight: bold;
 
    font-size: 17px;
 
}
 
 
.notification-table{
 
	border: 1px solid #ccc;
 
    -webkit-border-radius: 6px 6px 6px 6px;
 
    -moz-border-radius: 6px 6px 6px 6px;
 
    border-radius: 6px 6px 6px 6px;
 
    clear: both;
 
    margin: 0px 20px 0px 20px;
 
}
 
.notification-header .delete-notifications{
 
    float: right;
 
    padding-top: 8px;
 
    cursor: pointer;
 
}
 
.notification-subject{
 
    clear:both;
 
    border-bottom: 1px solid #eee;
 
    padding:5px 0px 5px 38px;
 
}
 
 
.notification-body{
 
	clear:both;
 
	margin: 34px 2px 2px 8px
 
}
 
 
/****
 
  PERMS
 
*****/
 
#perms .perms_section_head {
 
   padding:10px 10px 10px 0px;
 
   font-size:16px;
 
   font-weight: bold;
 
}
 
 
#perms .perm_tag{
 
  padding: 1px 3px 1px 3px;
 
  font-size: 10px;
 
  font-weight: bold;
 
  text-transform: uppercase;
 
  white-space: nowrap;
 
  -webkit-border-radius: 3px;
 
  -moz-border-radius: 3px;
 
  border-radius: 3px;
 
}
 
 
#perms .perm_tag.admin{
 
  background-color: #B94A48;
 
  color: #ffffff;
 
}
 
 
#perms .perm_tag.write{
 
  background-color: #B94A48;
 
  color: #ffffff;    
 
}
 
 
#perms .perm_tag.read{
 
  background-color: #468847;
 
  color: #ffffff;    
 
}
 
 
#perms .perm_tag.none{
 
  background-color: #bfbfbf;
 
  color: #ffffff;    
 
}
 
 
.perm-gravatar{
 
	vertical-align:middle;
 
	padding:2px;
 
}
 
.perm-gravatar-ac{
 
    vertical-align:middle;
 
    padding:2px;
 
    width: 14px;
 
    height: 14px;	
 
}
 
 
/*****************************************************************************
 
                                  DIFFS CSS
 
******************************************************************************/
 
 
div.diffblock {
 
    overflow: auto;
 
    padding: 0px;
 
    border: 1px solid #ccc;
 
    background: #f8f8f8;
 
    font-size: 100%;
 
    line-height: 100%;
 
    /* new */
 
    line-height: 125%;
 
    -webkit-border-radius: 6px 6px 0px 0px;
 
    -moz-border-radius: 6px 6px 0px 0px;
 
    border-radius: 6px 6px 0px 0px;     
 
}
 
div.diffblock.margined{
 
    margin: 0px 20px 0px 20px;
 
}
 
div.diffblock .code-header{
 
    border-bottom: 1px solid #CCCCCC;
 
    background: #EEEEEE;
 
    padding:10px 0 10px 0;
 
    height: 14px;
 
}
 
div.diffblock .code-header.cv{
 
    height: 34px;
 
}
 
div.diffblock .code-header-title{
 
	padding: 0px 0px 10px 5px !important;
 
	margin: 0 !important;
 
}
 
div.diffblock .code-header .hash{
 
    float: left;
 
    padding: 2px 0 0 2px;
 
}
 
div.diffblock .code-header .date{
 
    float:left;
 
    text-transform: uppercase;
 
    padding: 2px 0px 0px 2px;
 
}
 
div.diffblock .code-header div{
 
    margin-left:4px;
 
    font-weight: bold;
rhodecode/public/js/rhodecode.js
Show inline comments
 
@@ -102,381 +102,589 @@ ColorGenerator.prototype = {
 
        HSV_tuple = [this.CURRENT_RATIO, this.HSV_1, this.HSV_2]
 
        RGB_tuple = this._hsvToRgb(HSV_tuple[0],HSV_tuple[1],HSV_tuple[2]);
 
        function toRgb(v){
 
        	return ""+parseInt(v*256)
 
        }
 
        return [toRgb(RGB_tuple[0]),toRgb(RGB_tuple[1]),toRgb(RGB_tuple[2])];
 
        
 
    }
 
}
 

	
 

	
 

	
 

	
 

	
 
/**
 
 * GLOBAL YUI Shortcuts
 
 */
 
var YUC = YAHOO.util.Connect;
 
var YUD = YAHOO.util.Dom;
 
var YUE = YAHOO.util.Event;
 
var YUQ = YAHOO.util.Selector.query;
 

	
 
// defines if push state is enabled for this browser ?
 
var push_state_enabled = Boolean(
 
		window.history && window.history.pushState && window.history.replaceState
 
		&& !(   /* disable for versions of iOS before version 4.3 (8F190) */
 
				(/ Mobile\/([1-7][a-z]|(8([abcde]|f(1[0-8]))))/i).test(navigator.userAgent)
 
				/* disable for the mercury iOS browser, or at least older versions of the webkit engine */
 
				|| (/AppleWebKit\/5([0-2]|3[0-2])/i).test(navigator.userAgent)
 
		)
 
);
 

	
 
var _run_callbacks = function(callbacks){
 
	if (callbacks !== undefined){
 
		var _l = callbacks.length;
 
	    for (var i=0;i<_l;i++){
 
	    	var func = callbacks[i];
 
	    	if(typeof(func)=='function'){
 
	            try{
 
	          	    func();
 
	            }catch (err){};            		
 
	    	}
 
	    }
 
	}		
 
}
 

	
 
/**
 
 * Partial Ajax Implementation
 
 * 
 
 * @param url: defines url to make partial request
 
 * @param container: defines id of container to input partial result
 
 * @param s_call: success callback function that takes o as arg
 
 *  o.tId
 
 *  o.status
 
 *  o.statusText
 
 *  o.getResponseHeader[ ]
 
 *  o.getAllResponseHeaders
 
 *  o.responseText
 
 *  o.responseXML
 
 *  o.argument
 
 * @param f_call: failure callback
 
 * @param args arguments 
 
 */
 
function ypjax(url,container,s_call,f_call,args){
 
	var method='GET';
 
	if(args===undefined){
 
		args=null;
 
	}
 
	
 
	// Set special header for partial ajax == HTTP_X_PARTIAL_XHR
 
	YUC.initHeader('X-PARTIAL-XHR',true);
 
	
 
	// wrapper of passed callback
 
	var s_wrapper = (function(o){
 
		return function(o){
 
			YUD.get(container).innerHTML=o.responseText;
 
			YUD.setStyle(container,'opacity','1.0');
 
    		//execute the given original callback
 
    		if (s_call !== undefined){
 
    			s_call(o);
 
    		}
 
		}
 
	})()	
 
	YUD.setStyle(container,'opacity','0.3');
 
	YUC.asyncRequest(method,url,{
 
		success:s_wrapper,
 
		failure:function(o){
 
			console.log(o);
 
			YUD.get(container).innerHTML='ERROR';
 
			YUD.setStyle(container,'opacity','1.0');
 
			YUD.setStyle(container,'color','red');
 
		}
 
	},args);
 
	
 
};
 

	
 
var ajaxPOST = function(url,postData,success) {
 
	// Set special header for ajax == HTTP_X_PARTIAL_XHR
 
	YUC.initHeader('X-PARTIAL-XHR',true);
 
	
 
	var toQueryString = function(o) {
 
	    if(typeof o !== 'object') {
 
	        return false;
 
	    }
 
	    var _p, _qs = [];
 
	    for(_p in o) {
 
	        _qs.push(encodeURIComponent(_p) + '=' + encodeURIComponent(o[_p]));
 
	    }
 
	    return _qs.join('&');
 
	};
 
	
 
    var sUrl = url;
 
    var callback = {
 
        success: success,
 
        failure: function (o) {
 
            alert("error");
 
        },
 
    };
 
    var postData = toQueryString(postData);
 
    var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, callback, postData);
 
    return request;
 
};
 

	
 

	
 
/**
 
 * tooltip activate
 
 */
 
var tooltip_activate = function(){
 
    function toolTipsId(){
 
        var ids = [];
 
        var tts = YUQ('.tooltip');
 
        for (var i = 0; i < tts.length; i++) {
 
            // if element doesn't not have and id 
 
        	//  autogenerate one for tooltip 
 
            if (!tts[i].id){
 
                tts[i].id='tt'+((i*100)+tts.length);
 
            }
 
            ids.push(tts[i].id);
 
        }
 
        return ids
 
    };
 
    var myToolTips = new YAHOO.widget.Tooltip("tooltip", {
 
        context: [[toolTipsId()],"tl","bl",null,[0,5]],
 
        monitorresize:false,
 
        xyoffset :[0,0],
 
        autodismissdelay:300000,
 
        hidedelay:5,
 
        showdelay:20,
 
    });
 
};
 

	
 
/**
 
 * show more
 
 */
 
var show_more_event = function(){
 
    YUE.on(YUD.getElementsByClassName('show_more'),'click',function(e){
 
        var el = e.target;
 
        YUD.setStyle(YUD.get(el.id.substring(1)),'display','');
 
        YUD.setStyle(el.parentNode,'display','none');
 
    });
 
};
 

	
 

	
 
/**
 
 * Quick filter widget
 
 * 
 
 * @param target: filter input target
 
 * @param nodes: list of nodes in html we want to filter.
 
 * @param display_element function that takes current node from nodes and
 
 *    does hide or show based on the node
 
 * 
 
 */
 
var q_filter = function(target,nodes,display_element){
 
	
 
	var nodes = nodes;
 
	var q_filter_field = YUD.get(target);
 
	var F = YAHOO.namespace(target);
 

	
 
	YUE.on(q_filter_field,'click',function(){
 
	   q_filter_field.value = '';
 
	});
 

	
 
	YUE.on(q_filter_field,'keyup',function(e){
 
	    clearTimeout(F.filterTimeout); 
 
	    F.filterTimeout = setTimeout(F.updateFilter,600); 
 
	});
 

	
 
	F.filterTimeout = null;
 

	
 
	var show_node = function(node){
 
		YUD.setStyle(node,'display','')
 
	}
 
	var hide_node = function(node){
 
		YUD.setStyle(node,'display','none');
 
	}
 
	
 
	F.updateFilter  = function() { 
 
	   // Reset timeout 
 
	   F.filterTimeout = null;
 
	   
 
	   var obsolete = [];
 
	   
 
	   var req = q_filter_field.value.toLowerCase();
 
	   
 
	   var l = nodes.length;
 
	   var i;
 
	   var showing = 0;
 
	   
 
       for (i=0;i<l;i++ ){
 
    	   var n = nodes[i];
 
    	   var target_element = display_element(n)
 
    	   if(req && n.innerHTML.toLowerCase().indexOf(req) == -1){
 
    		   hide_node(target_element);
 
    	   }
 
    	   else{
 
    		   show_node(target_element);
 
    		   showing+=1;
 
    	   }
 
       }	  	   
 

	
 
	   // if repo_count is set update the number
 
	   var cnt = YUD.get('repo_count');
 
	   if(cnt){
 
		   YUD.get('repo_count').innerHTML = showing;
 
	   }       
 
       
 
	}	
 
};
 

	
 
var ajaxPOST = function(url,postData,success) {
 
    var sUrl = url;
 
    var callback = {
 
        success: success,
 
        failure: function (o) {
 
            alert("error");
 
        },
 
var tableTr = function(cls,body){
 
	var tr = document.createElement('tr');
 
	YUD.addClass(tr, cls);
 
	
 
	
 
	var cont = new YAHOO.util.Element(body);
 
	var comment_id = fromHTML(body).children[0].id.split('comment-')[1];
 
	tr.id = 'comment-tr-{0}'.format(comment_id);
 
	tr.innerHTML = '<td class="lineno-inline new-inline"></td>'+
 
    				 '<td class="lineno-inline old-inline"></td>'+ 
 
                     '<td>{0}</td>'.format(body);
 
	return tr;
 
    };
 
    var postData = postData;
 
    var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, callback, postData);
 
};
 

	
 

	
 
/** comments **/
 
var removeInlineForm = function(form) {
 
	form.parentNode.removeChild(form);
 
};
 

	
 
var tableTr = function(cls,body){
 
	var form = document.createElement('tr');
 
	YUD.addClass(form, cls);
 
	form.innerHTML = '<td class="lineno-inline new-inline"></td>'+
 
    				 '<td class="lineno-inline old-inline"></td>'+ 
 
                     '<td>{0}</td>'.format(body);
 
	return form;
 
};
 

	
 
var createInlineForm = function(parent_tr, f_path, line) {
 
	var tmpl = YUD.get('comment-inline-form-template').innerHTML;
 
	tmpl = tmpl.format(f_path, line);
 
	var form = tableTr('comment-form-inline',tmpl)
 
	
 
	// create event for hide button
 
	form = new YAHOO.util.Element(form);
 
	var form_hide_button = new YAHOO.util.Element(form.getElementsByClassName('hide-inline-form')[0]);
 
	form_hide_button.on('click', function(e) {
 
		var newtr = e.currentTarget.parentNode.parentNode.parentNode.parentNode.parentNode;
 
		if(YUD.hasClass(newtr.nextElementSibling,'inline-comments-button')){
 
			YUD.setStyle(newtr.nextElementSibling,'display','');
 
		}
 
		removeInlineForm(newtr);
 
		YUD.removeClass(parent_tr, 'form-open');
 
		
 
	});
 
	
 
	return form
 
};
 

	
 
/**
 
 * Inject inline comment for on given TR this tr should be always an .line
 
 * tr containing the line. Code will detect comment, and always put the comment
 
 * block at the very bottom
 
 */
 
var injectInlineForm = function(tr){
 
	  if(!YUD.hasClass(tr, 'line')){
 
		  return
 
	  }
 
	  var submit_url = AJAX_COMMENT_URL;
 
	  if(YUD.hasClass(tr,'form-open') || YUD.hasClass(tr,'context') || YUD.hasClass(tr,'no-comment')){
 
		  return
 
	  }	
 
	  YUD.addClass(tr,'form-open');
 
	  var node = tr.parentNode.parentNode.parentNode.getElementsByClassName('full_f_path')[0];
 
	  var f_path = YUD.getAttribute(node,'path');
 
	  var lineno = getLineNo(tr);
 
	  var form = createInlineForm(tr, f_path, lineno);
 
	  var target_tr = tr;
 
	  if(YUD.hasClass(YUD.getNextSibling(tr),'inline-comments')){
 
		  target_tr = YUD.getNextSibling(tr);
 
	  var form = createInlineForm(tr, f_path, lineno, submit_url);
 
	  
 
	  var parent = tr;
 
	  while (1){
 
		  var n = parent.nextElementSibling;
 
		  // next element are comments !
 
		  if(YUD.hasClass(n,'inline-comments')){
 
			  parent = n;
 
		  }
 
		  else{
 
			  break;
 
		  }
 
	  }
 
	  YUD.insertAfter(form,target_tr);
 
	  YUD.insertAfter(form,parent);
 
	  
 
	  YUD.get('text_'+lineno).focus();
 
	  var f = YUD.get(form);
 
	  
 
	  var overlay = f.getElementsByClassName('overlay')[0];
 
	  var _form = f.getElementsByClassName('inline-form')[0];
 
	  
 
	  form.on('submit',function(e){
 
		  YUE.preventDefault(e);
 
		  
 
		  //ajax submit
 
		  var text = YUD.get('text_'+lineno).value;
 
		  var postData = {
 
	            'text':text,
 
	            'f_path':f_path,
 
	            'line':lineno
 
		  };
 
		  
 
		  if(lineno === undefined){
 
			  alert('missing line !');
 
			  return
 
		  }
 
		  if(f_path === undefined){
 
			  alert('missing file path !');
 
			  return
 
		  }
 
		  
 
		  if(text == ""){
 
			  return
 
		  }
 
		  
 
		  var success = function(o){
 
			  YUD.removeClass(tr, 'form-open');
 
			  removeInlineForm(f);			  
 
			  var json_data = JSON.parse(o.responseText);
 
	          renderInlineComment(json_data);
 
		  };
 

	
 
		  if (YUD.hasClass(overlay,'overlay')){
 
			  var w = _form.offsetWidth;
 
			  var h = _form.offsetHeight;
 
			  YUD.setStyle(overlay,'width',w+'px');
 
			  YUD.setStyle(overlay,'height',h+'px');
 
		  }		  
 
		  YUD.addClass(overlay, 'submitting');		  
 
		  
 
		  ajaxPOST(submit_url, postData, success);
 
	  });
 
	  
 
	  tooltip_activate();
 
};
 

	
 
var createInlineAddButton = function(tr,label){
 
	var html = '<div class="add-comment"><span class="ui-btn">{0}</span></div>'.format(label);
 
var deleteComment = function(comment_id){
 
	var url = AJAX_COMMENT_DELETE_URL.replace('__COMMENT_ID__',comment_id);
 
    var postData = {'_method':'delete'};
 
    var success = function(o){
 
        var n = YUD.get('comment-tr-'+comment_id);
 
        var root = n.previousElementSibling.previousElementSibling;
 
        n.parentNode.removeChild(n);
 
        
 
	var add = new YAHOO.util.Element(tableTr('inline-comments-button',html));
 
        // scann nodes, and attach add button to last one
 
        placeAddButton(root);
 
    }
 
    ajaxPOST(url,postData,success);
 
}
 

	
 

	
 
var createInlineAddButton = function(tr){
 

	
 
	var label = TRANSLATION_MAP['add another comment'];
 
	
 
	var html_el = document.createElement('div');
 
	YUD.addClass(html_el, 'add-comment');
 
	html_el.innerHTML = '<span class="ui-btn">{0}</span>'.format(label);
 
	
 
	var add = new YAHOO.util.Element(html_el);
 
	add.on('click', function(e) {
 
		injectInlineForm(tr);
 
	});
 
	return add;
 
};
 

	
 
var getLineNo = function(tr) {
 
	var line;
 
	var o = tr.children[0].id.split('_');
 
	var n = tr.children[1].id.split('_');
 

	
 
	if (n.length >= 2) {
 
		line = n[n.length-1];
 
	} else if (o.length >= 2) {
 
		line = o[o.length-1];
 
	}
 

	
 
	return line
 
};
 

	
 
var placeAddButton = function(target_tr){
 
	if(!target_tr){
 
		return
 
	}
 
	var last_node = target_tr;
 
    //scann	
 
	  while (1){
 
		  var n = last_node.nextElementSibling;
 
		  // next element are comments !
 
		  if(YUD.hasClass(n,'inline-comments')){
 
			  last_node = n;
 
			  //also remove the comment button from previos
 
			  var comment_add_buttons = last_node.getElementsByClassName('add-comment');
 
			  for(var i=0;i<comment_add_buttons.length;i++){
 
				  var b = comment_add_buttons[i];
 
				  b.parentNode.removeChild(b);
 
			  }
 
		  }
 
		  else{
 
			  break;
 
		  }
 
	  }
 
	  
 
    var add = createInlineAddButton(target_tr);
 
    // get the comment div
 
    var comment_block = last_node.getElementsByClassName('comment')[0];
 
    // attach add button
 
    YUD.insertAfter(add,comment_block);	
 
}
 

	
 
/**
 
 * Places the inline comment into the changeset block in proper line position
 
 */
 
var placeInline = function(target_container,lineno,html){
 
	  var lineid = "{0}_{1}".format(target_container,lineno);
 
	  var target_line = YUD.get(lineid);
 
	  var comment = new YAHOO.util.Element(tableTr('inline-comments',html))
 
	  
 
	  // check if there are comments already !
 
	  var parent = target_line.parentNode;
 
	  var root_parent = parent;
 
	  while (1){
 
		  var n = parent.nextElementSibling;
 
		  // next element are comments !
 
		  if(YUD.hasClass(n,'inline-comments')){
 
			  parent = n;
 
		  }
 
		  else{
 
			  break;
 
		  }
 
	  }
 
	  // put in the comment at the bottom
 
	  YUD.insertAfter(comment,parent);
 
	  
 
	  // scann nodes, and attach add button to last one
 
      placeAddButton(root_parent);
 

	
 
	  return target_line;
 
}
 

	
 
/**
 
 * make a single inline comment and place it inside
 
 */
 
var renderInlineComment = function(json_data){
 
    try{
 
	  var html =  json_data['rendered_text'];
 
	  var lineno = json_data['line_no'];
 
	  var target_id = json_data['target_id'];
 
	  placeInline(target_id, lineno, html);
 

	
 
    }catch(e){
 
  	  console.log(e);
 
    }
 
}
 

	
 
/**
 
 * Iterates over all the inlines, and places them inside proper blocks of data
 
 */
 
var renderInlineComments = function(file_comments){
 
	for (f in file_comments){
 
        // holding all comments for a FILE
 
		var box = file_comments[f];
 

	
 
		var target_id = YUD.getAttribute(box,'target_id');
 
		// actually comments with line numbers
 
        var comments = box.children;
 
        for(var i=0; i<comments.length; i++){
 
        	var data = {
 
        		'rendered_text': comments[i].outerHTML,
 
        		'line_no': YUD.getAttribute(comments[i],'line'),
 
        		'target_id': target_id
 
        	}
 
        	renderInlineComment(data);
 
        }
 
    }	
 
}
 

	
 

	
 
var fileBrowserListeners = function(current_url, node_list_url, url_base,
 
									truncated_lbl, nomatch_lbl){
 
	var current_url_branch = +"?branch=__BRANCH__";
 
	var url = url_base;
 
	var node_url = node_list_url;	
 

	
 
	YUE.on('stay_at_branch','click',function(e){
 
	    if(e.target.checked){
 
	        var uri = current_url_branch;
 
	        uri = uri.replace('__BRANCH__',e.target.value);
 
	        window.location = uri;
 
	    }
 
	    else{
 
	        window.location = current_url;
 
	    }
 
	})            
 

	
 
	var n_filter = YUD.get('node_filter');
 
	var F = YAHOO.namespace('node_filter');
 
	
 
	F.filterTimeout = null;
 
	var nodes = null;
 

	
 
	F.initFilter = function(){
 
	  YUD.setStyle('node_filter_box_loading','display','');
 
	  YUD.setStyle('search_activate_id','display','none');
 
	  YUD.setStyle('add_node_id','display','none');
 
	  YUC.initHeader('X-PARTIAL-XHR',true);
 
	  YUC.asyncRequest('GET',url,{
 
	      success:function(o){
 
	        nodes = JSON.parse(o.responseText);
 
	        YUD.setStyle('node_filter_box_loading','display','none');
 
	        YUD.setStyle('node_filter_box','display','');
 
	        n_filter.focus();
 
			if(YUD.hasClass(n_filter,'init')){
 
				n_filter.value = '';
 
				YUD.removeClass(n_filter,'init');
 
			}   
 
	      },
 
	      failure:function(o){
 
	          console.log('failed to load');
 
	      }
 
	  },null);            
 
	}
 

	
 
	F.updateFilter  = function(e) {
 
	    
 
	    return function(){
 
	        // Reset timeout 
 
	        F.filterTimeout = null;
 
	        var query = e.target.value.toLowerCase();
 
	        var match = [];
 
	        var matches = 0;
 
	        var matches_max = 20;
 
	        if (query != ""){
 
	            for(var i=0;i<nodes.length;i++){
 
	            	
 
	                var pos = nodes[i].name.toLowerCase().indexOf(query)
 
	                if(query && pos != -1){
 
	                    
 
	                    matches++
 
	                    //show only certain amount to not kill browser 
 
	                    if (matches > matches_max){
 
	                        break;
 
	                    }
 
	                    
 
	                    var n = nodes[i].name;
 
	                    var t = nodes[i].type;
 
	                    var n_hl = n.substring(0,pos)
 
	                      +"<b>{0}</b>".format(n.substring(pos,pos+query.length))
 
	                      +n.substring(pos+query.length)                    
 
	                    match.push('<tr><td><a class="browser-{0}" href="{1}">{2}</a></td><td colspan="5"></td></tr>'.format(t,node_url.replace('__FPATH__',n),n_hl));
 
	                }
 
	                if(match.length >= matches_max){
 
	                    match.push('<tr><td>{0}</td><td colspan="5"></td></tr>'.format(truncated_lbl));
 
	                }
 
	                
 
	            }                       
 
	        }
 
	        if(query != ""){
 
	            YUD.setStyle('tbody','display','none');
 
	            YUD.setStyle('tbody_filtered','display','');
 
	            
 
	            if (match.length==0){
 
	              match.push('<tr><td>{0}</td><td colspan="5"></td></tr>'.format(nomatch_lbl));
 
	            }                           
 
	            
 
	            YUD.get('tbody_filtered').innerHTML = match.join("");   
 
	        }
 
	        else{
 
	            YUD.setStyle('tbody','display','');
 
	            YUD.setStyle('tbody_filtered','display','none');
 
	        }
 
	        
 
	    }
rhodecode/templates/admin/notifications/notifications.html
Show inline comments
 
## -*- coding: utf-8 -*-
 
<%inherit file="/base/base.html"/>
 

	
 
<%def name="title()">
 
    ${_('My Notifications')} ${c.rhodecode_user.username} - ${c.rhodecode_name}
 
</%def>
 

	
 
<%def name="breadcrumbs_links()">
 
    ${_('My Notifications')}
 
</%def>
 

	
 
<%def name="page_nav()">
 
	${self.menu('admin')}
 
</%def>
 

	
 
<%def name="main()">
 
<div class="box">
 
    <!-- box / title -->
 
    <div class="title">
 
        ${self.breadcrumbs()}
 
        ##<ul class="links">
 
        ##    <li>
 
        ##      <span style="text-transform: uppercase;"><a href="#">${_('Compose message')}</a></span>
 
        ##    </li>
 
        ##</ul>
 
    </div>
 
    %if c.notifications:
 
      <div style="padding:10px 15px;text-align: right">
 
      <div style="padding:14px 18px;text-align: right;float:right">
 
      <span id='mark_all_read' class="ui-btn">${_('Mark all read')}</span>
 
      </div>
 
    %endif
 
  <div id='notification_data'>
 
    <%include file='notifications_data.html'/>
 
  </div>
 
</div>
 
<script type="text/javascript">
 
var url_del = "${url('notification', notification_id='__NOTIFICATION_ID__')}";
 
YUE.on(YUQ('.delete-notification'),'click',function(e){
 
 var notification_id = e.currentTarget.id;
 
 deleteNotification(url_del,notification_id)
 
})
 
 YUE.on('mark_all_read','click',function(e){
 
	    var url = "${h.url('notifications_mark_all_read')}";
 
	    ypjax(url,'notification_data',function(){
 
	    	YUD.get('notification_counter').innerHTML=0;
 
    	var notification_counter = YUD.get('notification_counter');
 
    	if(notification_counter){
 
    		notification_counter.innerHTML=0;
 
    	}
 
	    	YUE.on(YUQ('.delete-notification'),'click',function(e){
 
	    		 var notification_id = e.currentTarget.id;
 
	    		 deleteNotification(url_del,notification_id)
 
	    	})
 
	    });
 
 })
 
</script>
 
</%def>
rhodecode/templates/admin/notifications/notifications_data.html
Show inline comments
 

	
 
%if c.notifications:
 
<%
 
unread = lambda n:{False:'unread'}.get(n)
 
%>
 
<div class="table">
 
  <div class="notification-list">
 
<div class="notification-paginator">
 
  <div class="pagination-wh pagination-left">
 
  ${c.notifications.pager('$link_previous ~2~ $link_next')}
 
  </div>
 
</div>
 

	
 
<div class="notification-list  notification-table">
 
  %for notification in c.notifications:
 
    <div id="notification_${notification.notification.notification_id}" class="container ${unread(notification.read)}">
 
      <div class="notification-header">
 
        <div class="gravatar">
 
            <img alt="gravatar" src="${h.gravatar_url(h.email(notification.notification.created_by_user.email),24)}"/>
 
        </div>
 
        <div class="desc ${unread(notification.read)}">
 
        <a href="${url('notification', notification_id=notification.notification.notification_id)}">${notification.notification.description}</a>
 
        </div>
 
        <div class="delete-notifications">
 
          <span id="${notification.notification.notification_id}" class="delete-notification delete_icon action"></span>
 
        </div>
 
      </div>
 
      <div class="notification-subject">${h.literal(notification.notification.subject)}</div>
 
    </div>
 
  %endfor
 
  </div>
 

	
 
<div class="notification-paginator">
 
  <div class="pagination-wh pagination-left">
 
  ${c.notifications.pager('$link_previous ~2~ $link_next')}
 
</div>
 
</div>
 

	
 
%else:
 
    <div class="table">${_('No notifications here yet')}</div>
 
%endif
rhodecode/templates/admin/notifications/show_notification.html
Show inline comments
 
## -*- coding: utf-8 -*-
 
<%inherit file="/base/base.html"/>
 

	
 
<%def name="title()">
 
    ${_('Show notification')} ${c.rhodecode_user.username} - ${c.rhodecode_name}
 
</%def>
 

	
 
<%def name="breadcrumbs_links()">
 
    ${h.link_to(_('Notifications'),h.url('notifications'))}
 
    &raquo;
 
    ${_('Show notification')}
 
</%def>
 

	
 
<%def name="page_nav()">
 
    ${self.menu('admin')}
 
</%def>
 

	
 
<%def name="main()">
 
<div class="box">
 
    <!-- box / title -->
 
    <div class="title">
 
        ${self.breadcrumbs()}
 
        ##<ul class="links">
 
        ##    <li>
 
        ##      <span style="text-transform: uppercase;"><a href="#">${_('Compose message')}</a></span>
 
        ##    </li>
 
        ##</ul>
 
    </div>
 
    <div class="table">
 
      <div id="notification_${c.notification.notification_id}">
 
        <div class="notification-header">
 
          <div class="gravatar">
 
              <img alt="gravatar" src="${h.gravatar_url(h.email(c.notification.created_by_user.email),24)}"/>
 
          </div>
 
          <div class="desc">
 
              ${c.notification.description}
 
          </div>
 
          <div class="delete-notifications">
 
            <span id="${c.notification.notification_id}" class="delete-notification delete_icon action"></span>
 
          </div>
 
        </div>
 
        <div>${h.rst_w_mentions(c.notification.body)}</div>
 
        <div class="notification-body">${h.rst_w_mentions(c.notification.body)}</div>
 
      </div>
 
    </div>
 
</div>
 
<script type="text/javascript">
 
var url = "${url('notification', notification_id='__NOTIFICATION_ID__')}";
 
var main = "${url('notifications')}";
 
   YUE.on(YUQ('.delete-notification'),'click',function(e){
 
       var notification_id = e.currentTarget.id;
 
       deleteNotification(url,notification_id,[function(){window.location=main}])
 
   })
 
</script>
 
</%def>
rhodecode/templates/admin/settings/settings.html
Show inline comments
 
@@ -117,98 +117,130 @@
 

	
 
    <h3>${_('Mercurial settings')}</h3>
 
    ${h.form(url('admin_setting', setting_id='mercurial'),method='put')}
 
    <div class="form">
 
        <!-- fields -->
 

	
 
        <div class="fields">
 

	
 
             <div class="field">
 
                <div class="label label-checkbox">
 
                    <label>${_('Web')}:</label>
 
                </div>
 
                <div class="checkboxes">
 
					<div class="checkbox">
 
						${h.checkbox('web_push_ssl','true')}
 
						<label for="web_push_ssl">${_('require ssl for pushing')}</label>
 
					</div>
 
				</div>
 
             </div>
 

	
 
             <div class="field">
 
                <div class="label label-checkbox">
 
                    <label>${_('Hooks')}:</label>
 
                </div>
 
                <div class="checkboxes">
 
					<div class="checkbox">
 
						${h.checkbox('hooks_changegroup_update','True')}
 
						<label for="hooks_changegroup_update">${_('Update repository after push (hg update)')}</label>
 
					</div>
 
					<div class="checkbox">
 
						${h.checkbox('hooks_changegroup_repo_size','True')}
 
						<label for="hooks_changegroup_repo_size">${_('Show repository size after push')}</label>
 
					</div>
 
                    <div class="checkbox">
 
                        ${h.checkbox('hooks_pretxnchangegroup_push_logger','True')}
 
                        <label for="hooks_pretxnchangegroup_push_logger">${_('Log user push commands')}</label>
 
                    </div>
 
                    <div class="checkbox">
 
                        ${h.checkbox('hooks_preoutgoing_pull_logger','True')}
 
                        <label for="hooks_preoutgoing_pull_logger">${_('Log user pull commands')}</label>
 
                    </div>
 
				</div>
 
                <div class="input" style="margin-top:10px">
 
                    ${h.link_to(_('advanced setup'),url('admin_edit_setting',setting_id='hooks'),class_="ui-btn")}
 
                </div>
 
             </div>
 
            <div class="field">
 
                <div class="label">
 
                    <label for="paths_root_path">${_('Repositories location')}:</label>
 
                </div>
 
                <div class="input">
 
                    ${h.text('paths_root_path',size=30,readonly="readonly")}
 
					<span id="path_unlock" class="tooltip"
 
						title="${h.tooltip(_('This a crucial application setting. If you are really sure you need to change this, you must restart application in order to make this setting take effect. Click this label to unlock.'))}">
 
		                ${_('unlock')}</span>
 
                </div>
 
            </div>
 

	
 
            <div class="buttons">
 
                ${h.submit('save',_('Save settings'),class_="ui-button")}
 
                ${h.reset('reset',_('Reset'),class_="ui-button")}
 
           </div>
 
        </div>
 
    </div>
 
    ${h.end_form()}
 

	
 
    <script type="text/javascript">
 
        YAHOO.util.Event.onDOMReady(function(){
 
            YAHOO.util.Event.addListener('path_unlock','click',function(){
 
                YAHOO.util.Dom.get('paths_root_path').removeAttribute('readonly');
 
            });
 
        });
 
    </script>
 

	
 
    <h3>${_('Test Email')}</h3>
 
    ${h.form(url('admin_setting', setting_id='email'),method='put')}
 
    <div class="form">
 
        <!-- fields -->
 

	
 
        <div class="fields">
 
            <div class="field">
 
                <div class="label">
 
                    <label for="test_email">${_('Email to')}:</label>
 
                </div>
 
                <div class="input">
 
                    ${h.text('test_email',size=30)}
 
                </div>
 
            </div>
 

	
 
            <div class="buttons">
 
            ${h.submit('send',_('Send'),class_="ui-button")}
 
            </div>
 
        </div>
 
    </div>
 
    ${h.end_form()}
 

	
 
    <h3>${_('System Info and Packages')}</h3>
 
    <div class="form">
 
    <div>
 
        <h5 id="expand_modules" style="cursor: pointer">&darr; ${_('show')} &darr;</h5>
 
    </div>
 
      <div id="expand_modules_table"  style="display:none">
 
      <h5>Python - ${c.py_version}</h5>
 
      <h5>System - ${c.platform}</h5>
 

	
 
      <table class="table" style="margin:0px 0px 0px 20px">
 
          <colgroup>
 
              <col style="width:220px">
 
          </colgroup>
 
          <tbody>
 
              %for key, value in c.modules:
 
                  <tr>
 
                      <th style="text-align: right;padding-right:5px;">${key}</th>
 
                      <td>${value}</td>
 
                  </tr>
 
              %endfor
 
          </tbody>
 
      </table>
 
      </div>
 
    </div>
 

	
 
    <script type="text/javascript">
 
    YUE.on('expand_modules','click',function(e){
 
    	YUD.setStyle('expand_modules_table','display','');
 
    	YUD.setStyle('expand_modules','display','none');
 
    })
 
    </script>
 

	
 
</div>
 
</%def>
rhodecode/templates/base/root.html
Show inline comments
 
## -*- coding: utf-8 -*-
 
<!DOCTYPE html>
 
<html xmlns="http://www.w3.org/1999/xhtml">
 
    <head>
 
        <title>${self.title()}</title>
 
        <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
 
        <meta name="robots" content="index, nofollow"/>
 
        <link rel="icon" href="${h.url('/images/icons/database_gear.png')}" type="image/png" />
 

	
 
        ## CSS ###
 
        <%def name="css()">
 
            <link rel="stylesheet" type="text/css" href="${h.url('/css/style.css')}" media="screen"/>
 
            <link rel="stylesheet" type="text/css" href="${h.url('/css/pygments.css')}"/>
 
            ## EXTRA FOR CSS
 
            ${self.css_extra()}
 
        </%def>
 
        <%def name="css_extra()">
 
        </%def>
 

	
 
        ${self.css()}
 

	
 
        %if c.ga_code:
 
        <!-- Analytics -->
 
	     <script type="text/javascript">
 
	      var _gaq = _gaq || [];
 
	      _gaq.push(['_setAccount', '${c.ga_code}']);
 
	      _gaq.push(['_trackPageview']);
 

	
 
	      (function() {
 
	        var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
 
	        ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
 
	        var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
 
	      })();
 
	     </script>
 
	    %endif
 

	
 
        ## JAVASCRIPT ##
 
        <%def name="js()">
 
            <script type="text/javascript" src="${h.url('/js/yui.2.9.js')}"></script>
 
            <!--[if lt IE 9]>
 
               <script language="javascript" type="text/javascript" src="${h.url('/js/excanvas.min.js')}"></script>
 
            <![endif]-->
 
            <script type="text/javascript" src="${h.url('/js/yui.flot.js')}"></script>
 
            <script type="text/javascript" src="${h.url('/js/rhodecode.js')}"></script>
 
           ## EXTRA FOR JS
 
           ${self.js_extra()}
 

	
 
            <script type="text/javascript">
 
            var follow_base_url  = "${h.url('toggle_following')}";
 
            var stop_follow_text = "${_('Stop following this repository')}";
 
            var start_follow_text = "${_('Start following this repository')}";
 

	
 
            //JS translations map
 
            var TRANSLATION_MAP = {
 
            	'add another comment':'${_("add another comment")}',
 
                'Stop following this repository':"${_('Stop following this repository')}",
 
                'Start following this repository':"${_('Start following this repository')}",
 
            };
 

	
 
            var onSuccessFollow = function(target){
 
                var f = YUD.get(target.id);
 
                var f_cnt = YUD.get('current_followers_count');
 

	
 
                if(f.getAttribute('class')=='follow'){
 
                    f.setAttribute('class','following');
 
                    f.setAttribute('title',stop_follow_text);
 
                    f.setAttribute('title',TRANSLATION_MAP['Stop following this repository']);
 

	
 
                    if(f_cnt){
 
                        var cnt = Number(f_cnt.innerHTML)+1;
 
                        f_cnt.innerHTML = cnt;
 
                    }
 
                }
 
                else{
 
                    f.setAttribute('class','follow');
 
                    f.setAttribute('title',start_follow_text);
 
                    f.setAttribute('title',TRANSLATION_MAP['Start following this repository']);
 
                    if(f_cnt){
 
                        var cnt = Number(f_cnt.innerHTML)+1;
 
                        f_cnt.innerHTML = cnt;
 
                    }
 
                }
 
            }
 

	
 
            var toggleFollowingUser = function(target,fallows_user_id,token,user_id){
 
                args = 'follows_user_id='+fallows_user_id;
 
                args+= '&amp;auth_token='+token;
 
                if(user_id != undefined){
 
                    args+="&amp;user_id="+user_id;
 
                }
 
                YUC.asyncRequest('POST',follow_base_url,{
 
                    success:function(o){
 
                    	onSuccessFollow(target);
 
                    }
 
                },args);
 
                return false;
 
            }
 

	
 
            var toggleFollowingRepo = function(target,fallows_repo_id,token,user_id){
 

	
 
                args = 'follows_repo_id='+fallows_repo_id;
 
                args+= '&amp;auth_token='+token;
 
                if(user_id != undefined){
 
                    args+="&amp;user_id="+user_id;
 
                }
 
                YUC.asyncRequest('POST',follow_base_url,{
 
                    success:function(o){
 
                    	onSuccessFollow(target);
 
                    }
 
                },args);
 
                return false;
 
            }
 
           YUE.onDOMReady(function(){
 
             tooltip_activate();
 
             show_more_event();
 

	
 
             YUE.on('quick_login_link','click',function(e){
 
                 // make sure we don't redirect
 
                 YUE.preventDefault(e);
 

	
 
                 if(YUD.hasClass('quick_login_link','enabled')){
 
                     YUD.setStyle('quick_login','display','none');
 
                     YUD.removeClass('quick_login_link','enabled');
 
                 }
 
                 else{
 
                     YUD.setStyle('quick_login','display','');
 
                     YUD.addClass('quick_login_link','enabled');
 
                     var usr = YUD.get('username');
 
                     if(usr){
 
                    	 usr.focus();
 
                     }
 
                 }
 
             });
 
           })
 
            </script>
 
        </%def>
 
        <%def name="js_extra()"></%def>
 
        ${self.js()}
 
    </head>
 
    <body id="body">
 
     ## IE hacks
 
      <!--[if IE 7]>
 
      <script>YUD.addClass(document.body,'ie7')</script>
 
      <![endif]-->            
 
      <!--[if IE 8]>
 
      <script>YUD.addClass(document.body,'ie8')</script>
 
      <![endif]-->
 
      <!--[if IE 9]>
 
      <script>YUD.addClass(document.body,'ie9')</script>
 
      <![endif]-->    
 

	
 
      ${next.body()}
 
    </body>
 
</html>
rhodecode/templates/changelog/changelog.html
Show inline comments
 
## -*- coding: utf-8 -*-
 

	
 
<%inherit file="/base/base.html"/>
 

	
 
<%def name="title()">
 
${c.repo_name} ${_('Changelog')} - ${c.rhodecode_name}
 
</%def>
 

	
 
<%def name="breadcrumbs_links()">
 
    ${h.link_to(u'Home',h.url('/'))}
 
    &raquo;
 
    ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
 
    &raquo;
 
    ${_('Changelog')} - ${_('showing ')} ${c.size if c.size <= c.total_cs else c.total_cs} ${_('out of')} ${c.total_cs} ${_('revisions')}
 
</%def>
 

	
 
<%def name="page_nav()">
 
	${self.menu('changelog')}
 
</%def>
 

	
 
<%def name="main()">
 
<div class="box">
 
    <!-- box / title -->
 
    <div class="title">
 
        ${self.breadcrumbs()}
 
    </div>
 
    <div class="table">
 
		% if c.pagination:
 
			<div id="graph">
 
				<div id="graph_nodes">
 
					<canvas id="graph_canvas"></canvas>
 
				</div>
 
				<div id="graph_content">
 
					<div class="container_header">
 
				        ${h.form(h.url.current(),method='get')}
 
				        <div class="info_box" style="float:left">
 
				          ${h.submit('set',_('Show'),class_="ui-btn")}
 
				          ${h.text('size',size=1,value=c.size)}
 
				          ${_('revisions')}
 
				        </div>
 
				        ${h.end_form()}
 
					<div id="rev_range_container" style="display:none"></div>
 
                    <div style="float:right">${h.select('branch_filter',c.branch_name,c.branch_filters)}</div>
 
					</div>
 

	
 
				%for cnt,cs in enumerate(c.pagination):
 
					<div id="chg_${cnt+1}" class="container ${'tablerow%s' % (cnt%2)}">
 
						<div class="left">
 
							<div>
 
							${h.checkbox(cs.short_id,class_="changeset_range")}
 
							<span class="tooltip" title="${h.age(cs.date)}"><a href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id)}"><span class="changeset_id">${cs.revision}:<span class="changeset_hash">${h.short_id(cs.raw_id)}</span></span></a></span>
 
							</div>
 
							<div class="author">
 
								<div class="gravatar">
 
									<img alt="gravatar" src="${h.gravatar_url(h.email(cs.author),16)}"/>
 
								</div>
 
								<div title="${cs.author}" class="user">${h.person(cs.author)}</div>
 
							</div>
 
                            <div class="date">${cs.date}</div>
 
						</div>
 
						<div class="mid">
 
                            <div class="message">${h.urlify_commit(h.wrap_paragraphs(cs.message),c.repo_name,h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}</div>
 
                            <div class="expand"><span class="expandtext">&darr; ${_('show more')} &darr;</span></div>
 
						</div>
 
						<div class="right">
 
									<div id="${cs.raw_id}_changes_info" class="changes">
 
									<div class="changes">
 
                                        <div id="${cs.raw_id}"  style="float:right;" class="changed_total tooltip" title="${_('Affected number of files, click to show more details')}">${len(cs.affected_files)}</div>
 
                                        <div class="comments-container">
 
                                        %if len(c.comments.get(cs.raw_id,[])) > 0:
 
                                            <div class="comments-cnt" title="${('comments')}">
 
                                              <a href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id,anchor='comment-%s' % c.comments[cs.raw_id][0].comment_id)}">
 
                                               <div class="comments-cnt">${len(c.comments[cs.raw_id])}</div>
 
                                               <img src="${h.url('/images/icons/comments.png')}">
 
                                              </a>
 
                                            </div>
 
                                        %endif
 
                                        </div>
 
									</div>
 
								   %if cs.parents:
 
									%for p_cs in reversed(cs.parents):
 
										<div class="parent">${_('Parent')}
 
											<span class="changeset_id">${p_cs.revision}:<span class="changeset_hash">${h.link_to(h.short_id(p_cs.raw_id),
 
											h.url('changeset_home',repo_name=c.repo_name,revision=p_cs.raw_id),title=p_cs.message)}</span></span>
 
										</div>
 
									%endfor
 
								   %else:
 
                                        <div class="parent">${_('No parents')}</div>
 
                                   %endif
 

	
 
								<span class="logtags">
 
									%if len(cs.parents)>1:
 
									<span class="merge">${_('merge')}</span>
 
									%endif
 
									%if h.is_hg(c.rhodecode_repo) and cs.branch:
 
									%if cs.branch:
 
									<span class="branchtag" title="${'%s %s' % (_('branch'),cs.branch)}">
 
									   ${h.link_to(h.shorter(cs.branch),h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}</span>
 
									   ${h.link_to(h.shorter(cs.branch),h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}
 
                                    </span>
 
									%endif
 
                                    %if h.is_hg(c.rhodecode_repo):
 
                                      %for book in cs.bookmarks:
 
                                      <span class="bookbook" title="${'%s %s' % (_('bookmark'),book)}">
 
                                         ${h.link_to(h.shorter(book),h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}
 
                                      </span>                   
 
                                      %endfor                
 
									%endif
 
									%for tag in cs.tags:
 
										<span class="tagtag"  title="${'%s %s' % (_('tag'),tag)}">
 
										${h.link_to(h.shorter(tag),h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}</span>
 
									%endfor
 
								</span>
 
						</div>
 
					</div>
 

	
 
				%endfor
 
				<div class="pagination-wh pagination-left">
 
					${c.pagination.pager('$link_previous ~2~ $link_next')}
 
				</div>
 
				</div>
 
			</div>
 

	
 
			<script type="text/javascript" src="${h.url('/js/graph.js')}"></script>
 
			<script type="text/javascript">
 
				YAHOO.util.Event.onDOMReady(function(){
 

	
 
                    //Monitor range checkboxes and build a link to changesets
 
                    //ranges
 
                    var checkboxes = YUD.getElementsByClassName('changeset_range');
 
                    var url_tmpl = "${h.url('changeset_home',repo_name=c.repo_name,revision='__REVRANGE__')}";
 
                    YUE.on(checkboxes,'click',function(e){
 
                        var checked_checkboxes = [];
 
                        for (pos in checkboxes){
 
                            if(checkboxes[pos].checked){
 
                                checked_checkboxes.push(checkboxes[pos]);
 
                            }
 
                        }
 
                        if(checked_checkboxes.length>1){
 
                        	var rev_end = checked_checkboxes[0].name;
 
                        	var rev_start = checked_checkboxes[checked_checkboxes.length-1].name;
 

	
 
                            var url = url_tmpl.replace('__REVRANGE__',
 
                            		rev_start+'...'+rev_end);
 

	
 
                        var link = "<a href="+url+">${_('Show selected changes __S -> __E')}</a>"
 
                        link = link.replace('__S',rev_start);
 
                        link = link.replace('__E',rev_end);
 
                        YUD.get('rev_range_container').innerHTML = link;
 
                        YUD.setStyle('rev_range_container','display','');
 
                        }
 
                        else{
 
                        	YUD.setStyle('rev_range_container','display','none');
 

	
 
                        }
 
                    });
 

	
 
                    var msgs = YUQ('.message');
 
                    // get first element height
 
                    var el = YUQ('#graph_content .container')[0];
 
                    var row_h = el.clientHeight;
 
                    for(var i=0;i<msgs.length;i++){
 
                    	var m = msgs[i];
 

	
 
                    	var h = m.clientHeight;
 
                    	var pad = YUD.getStyle(m,'padding');
 
                    	if(h > row_h){
 
                    		var offset = row_h - (h+12);
 
                    		YUD.setStyle(m.nextElementSibling,'display','block');
 
                    		YUD.setStyle(m.nextElementSibling,'margin-top',offset+'px');
 
                    	};
 
                    }
 
                    YUE.on(YUQ('.expand'),'click',function(e){
 
                    	var elem = e.currentTarget.parentNode.parentNode;
 
                    	YUD.setStyle(e.currentTarget,'display','none');
 
                    	YUD.setStyle(elem,'height','auto');
 

	
 
                    	//redraw the graph, max_w and jsdata are global vars
 
                        set_canvas(max_w);
 

	
 
                        var r = new BranchRenderer();
 
                        r.render(jsdata,max_w);
 

	
 
                    })
 

	
 
                    // Fetch changeset details
 
                    YUE.on(YUD.getElementsByClassName('changed_total'),'click',function(e){
 
                    	var id = e.currentTarget.id
 
                    	var url = "${h.url('changelog_details',repo_name=c.repo_name,cs='__CS__')}"
 
                    	var url = url.replace('__CS__',id);
 
                    	ypjax(url,id+'_changes_info',function(){tooltip_activate()});
 
                    	ypjax(url,id,function(){tooltip_activate()});
 
                    });
 

	
 
                    // change branch filter
 
                    YUE.on(YUD.get('branch_filter'),'change',function(e){
 
                    	var selected_branch = e.currentTarget.options[e.currentTarget.selectedIndex].value;
 
                    	var url_main = "${h.url('changelog_home',repo_name=c.repo_name)}";
 
                    	var url = "${h.url('changelog_home',repo_name=c.repo_name,branch='__BRANCH__')}";
 
                    	var url = url.replace('__BRANCH__',selected_branch);
 
                    	if(selected_branch != ''){
 
                    		window.location = url;
 
                    	}else{
 
                    		window.location = url_main;
 
                    	}
 

	
 
                    });
 

	
 
					function set_canvas(heads) {
 
						var c = document.getElementById('graph_nodes');
 
						var t = document.getElementById('graph_content');
 
						canvas = document.getElementById('graph_canvas');
 
						var div_h = t.clientHeight;
 
						c.style.height=div_h+'px';
 
						canvas.setAttribute('height',div_h);
 
						c.style.height=max_w+'px';
 
						canvas.setAttribute('width',max_w);
 
					};
 
					var heads = 1;
 
					var max_heads = 0;
 
					var jsdata = ${c.jsdata|n};
 

	
 
					for( var i=0;i<jsdata.length;i++){
 
					    var m = Math.max.apply(Math, jsdata[i][1]);
 
					    if (m>max_heads){
 
					        max_heads = m;
 
					    }
 
					}
 
					var max_w = Math.max(100,max_heads*25);
 
					set_canvas(max_w);
 

	
 
					var r = new BranchRenderer();
 
					r.render(jsdata,max_w);
 

	
 
				});
 
			</script>
 
		%else:
 
			${_('There are no changes yet')}
 
		%endif
 
    </div>
 
</div>
 
</%def>
rhodecode/templates/changeset/changeset.html
Show inline comments
 
## -*- coding: utf-8 -*-
 

	
 
<%inherit file="/base/base.html"/>
 

	
 
<%def name="title()">
 
    ${c.repo_name} ${_('Changeset')} - r${c.changeset.revision}:${h.short_id(c.changeset.raw_id)} - ${c.rhodecode_name}
 
</%def>
 

	
 
<%def name="breadcrumbs_links()">
 
    ${h.link_to(u'Home',h.url('/'))}
 
    &raquo;
 
    ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
 
    &raquo;
 
    ${_('Changeset')} - <span class='hash'>r${c.changeset.revision}:${h.short_id(c.changeset.raw_id)}</span>
 
</%def>
 

	
 
<%def name="page_nav()">
 
    ${self.menu('changelog')}
 
</%def>
 

	
 
<%def name="main()">
 
<div class="box">
 
    <!-- box / title -->
 
    <div class="title">
 
        ${self.breadcrumbs()}
 
    </div>
 
    <div class="table">
 
		<div class="diffblock">
 
			<div class="code-header">
 
                <div class="hash">
 
                 r${c.changeset.revision}:${h.short_id(c.changeset.raw_id)}
 
                </div>
 
                <div class="date">
 
                  ${c.changeset.date}
 
                </div>
 
                <div class="diff-actions">
 
                  <a href="${h.url('raw_changeset_home',repo_name=c.repo_name,revision=c.changeset.raw_id,diff='show')}" title="${_('raw diff')}" class="tooltip"><img class="icon" src="${h.url('/images/icons/page_white.png')}"/></a>
 
                  <a href="${h.url('raw_changeset_home',repo_name=c.repo_name,revision=c.changeset.raw_id,diff='download')}" title="${_('download diff')}" class="tooltip"><img class="icon" src="${h.url('/images/icons/page_white_get.png')}"/></a>
 
                  ${c.ignorews_url(request.GET)}
 
                  ${c.context_url(request.GET)}
 
                </div>
 
                <div class="comments-number" style="float:right;padding-right:5px">${len(c.comments)} comment(s) (${c.inline_cnt} ${_('inline')})</div>
 
			</div>
 
		</div>
 
	    <div id="changeset_content">
 
			<div class="container">
 
	             <div class="left">
 
	                 <div class="author">
 
	                     <div class="gravatar">
 
	                         <img alt="gravatar" src="${h.gravatar_url(h.email(c.changeset.author),20)}"/>
 
	                     </div>
 
	                     <span>${h.person(c.changeset.author)}</span><br/>
 
	                     <span><a href="mailto:${h.email_or_none(c.changeset.author)}">${h.email_or_none(c.changeset.author)}</a></span><br/>
 
	                 </div>
 
	                 <div class="message">${h.urlify_commit(h.wrap_paragraphs(c.changeset.message),c.repo_name)}</div>
 
	             </div>
 
	             <div class="right">
 
		             <div class="changes">
 
                        % if len(c.changeset.affected_files) <= c.affected_files_cut_off:
 
		                 <span class="removed" title="${_('removed')}">${len(c.changeset.removed)}</span>
 
		                 <span class="changed" title="${_('changed')}">${len(c.changeset.changed)}</span>
 
		                 <span class="added" title="${_('added')}">${len(c.changeset.added)}</span>
 
	                    % else:
 
                         <span class="removed" title="${_('affected %s files') % len(c.changeset.affected_files)}">!</span>
 
                         <span class="changed" title="${_('affected %s files') % len(c.changeset.affected_files)}">!</span>
 
                         <span class="added"   title="${_('affected %s files') % len(c.changeset.affected_files)}">!</span>
 
	                    % endif
 
		             </div>
 

	
 
		            %if c.changeset.parents:
 
		             %for p_cs in reversed(c.changeset.parents):
 
		                 <div class="parent">${_('Parent')}
 
                     <span class="changeset_id">${p_cs.revision}:<span class="changeset_hash">${h.link_to(h.short_id(p_cs.raw_id),
 
		                     h.url('changeset_home',repo_name=c.repo_name,revision=p_cs.raw_id),title=p_cs.message)}</span></span>
 
		                 </div>
 
		             %endfor
 
                    %else:
 
                        <div class="parent">${_('No parents')}</div>
 
                    %endif
 
		         <span class="logtags">
 
                 %if len(c.changeset.parents)>1:
 
                 <span class="merge">${_('merge')}</span>
 
                 %endif
 
		             %if c.changeset.branch:
 
		             <span class="branchtag" title="${'%s %s' % (_('branch'),c.changeset.branch)}">
 
		             ${h.link_to(c.changeset.branch,h.url('files_home',repo_name=c.repo_name,revision=c.changeset.raw_id))}</span>
 
		             ${h.link_to(c.changeset.branch,h.url('files_home',repo_name=c.repo_name,revision=c.changeset.raw_id))}
 
                     </span>
 
                     %endif
 
		             %for tag in c.changeset.tags:
 
		                 <span class="tagtag"  title="${'%s %s' % (_('tag'),tag)}">
 
		                 ${h.link_to(tag,h.url('files_home',repo_name=c.repo_name,revision=c.changeset.raw_id))}</span>
 
		             %endfor
 
		         </span>
 
	                </div>
 
	        </div>
 
	        <span>
 
	        ${_('%s files affected with %s insertions and %s deletions:') % (len(c.changeset.affected_files),c.lines_added,c.lines_deleted)}
 
	        </span>
 
	        <div class="cs_files">
 
	                %for change,filenode,diff,cs1,cs2,stat in c.changes:
 
	                    <div class="cs_${change}">
 
                            <div class="node">
 
                            %if change != 'removed':
 
                                ${h.link_to(h.safe_unicode(filenode.path),c.anchor_url(filenode.changeset.raw_id,filenode.path,request.GET)+"_target")}
 
                            %else:
 
                                ${h.link_to(h.safe_unicode(filenode.path),h.url.current(anchor=h.FID('',filenode.path)))}
 
                            %endif
 
                            </div>
 
		                    <div class="changes">${h.fancy_file_stats(stat)}</div>
 
	                    </div>
 
	                %endfor
 
	                % if c.cut_off:
 
	                  ${_('Changeset was too big and was cut off...')}
 
	                % endif
 
	        </div>
 
	    </div>
 

	
 
    </div>
 

	
 
    ## diff block
 
    <%namespace name="diff_block" file="/changeset/diff_block.html"/>
 
    ${diff_block.diff_block(c.changes)}
 

	
 
    ## template for inline comment form
 
    <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
 
    ${comment.comment_inline_form(c.changeset)}
 

	
 
    ## render comments
 
    ${comment.comments(c.changeset)}
 

	
 
    <script type="text/javascript">
 
      var deleteComment = function(comment_id){
 

	
 
          var url = "${url('changeset_comment_delete',repo_name=c.repo_name,comment_id='__COMMENT_ID__')}".replace('__COMMENT_ID__',comment_id);
 
          var postData = '_method=delete';
 
          var success = function(o){
 
              var n = YUD.get('comment-'+comment_id);
 
              n.parentNode.removeChild(n);
 
          }
 
          ajaxPOST(url,postData,success);
 
      }
 

	
 
      YUE.onDOMReady(function(){
 

	
 
    	  AJAX_COMMENT_URL = "${url('changeset_comment',repo_name=c.repo_name,revision=c.changeset.raw_id)}";
 
    	  AJAX_COMMENT_DELETE_URL = "${url('changeset_comment_delete',repo_name=c.repo_name,comment_id='__COMMENT_ID__')}"
 
          YUE.on(YUQ('.show-inline-comments'),'change',function(e){
 
              var show = 'none';
 
              var target = e.currentTarget;
 
              if(target.checked){
 
                  var show = ''
 
              }
 
              var boxid = YUD.getAttribute(target,'id_for');
 
              var comments = YUQ('#{0} .inline-comments'.format(boxid));
 
              for(c in comments){
 
                 YUD.setStyle(comments[c],'display',show);
 
              }
 
              var btns = YUQ('#{0} .inline-comments-button'.format(boxid));
 
              for(c in btns){
 
                  YUD.setStyle(btns[c],'display',show);
 
               }
 
          })
 

	
 
          YUE.on(YUQ('.line'),'click',function(e){
 
              var tr = e.currentTarget;
 
              injectInlineForm(tr);
 
          });
 

	
 
          // inject comments into they proper positions
 
          var file_comments = YUQ('.inline-comment-placeholder');
 

	
 
          for (f in file_comments){
 
              var box = file_comments[f];
 
              var inlines = box.children;
 
              for(var i=0; i<inlines.length; i++){
 
                  try{
 

	
 
                    var inline = inlines[i];
 
                    var lineno = YUD.getAttribute(inlines[i],'line');
 
                    var lineid = "{0}_{1}".format(YUD.getAttribute(inline,'target_id'),lineno);
 
                    var target_line = YUD.get(lineid);
 

	
 
                    var add = createInlineAddButton(target_line.parentNode,'${_("add another comment")}');
 
                    YUD.insertAfter(add,target_line.parentNode);
 

	
 
                    var comment = new YAHOO.util.Element(tableTr('inline-comments',inline.innerHTML))
 
                    YUD.insertAfter(comment,target_line.parentNode);
 
                  }catch(e){
 
                	  console.log(e);
 
                  }
 
              }
 
          }
 
          renderInlineComments(file_comments);
 
      })
 

	
 
    </script>
 

	
 
    </div>
 
</%def>
rhodecode/templates/changeset/changeset_comment_block.html
Show inline comments
 
new file 100644
 
<%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
 
${comment.comment_block(c.co)}
rhodecode/templates/changeset/changeset_file_comment.html
Show inline comments
 
## -*- coding: utf-8 -*-
 
## usage:
 
## <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
 
## ${comment.comment_block(co)}
 
##
 
<%def name="comment_block(co)">
 
  <div class="comment" id="comment-${co.comment_id}">
 
  <div class="comment" id="comment-${co.comment_id}" line="${co.line_no}">
 
    <div class="comment-wrapp">
 
  	<div class="meta">
 
  		<span class="user">
 
  			<img src="${h.gravatar_url(co.author.email, 20)}" />
 
  			${co.author.username}
 
  		</span>
 
  		<span class="date">
 
  			${h.age(co.modified_at)}
 
  		</span>
 
      %if h.HasPermissionAny('hg.admin', 'repository.admin')() or co.author.user_id == c.rhodecode_user.user_id:
 
        <span class="buttons">
 
          <span onClick="deleteComment(${co.comment_id})" class="delete-comment ui-btn">${_('Delete')}</span>
 
        </span>
 
      %endif
 
  	</div>
 
  	<div class="text">
 
  		${h.rst_w_mentions(co.text)|n}
 
  	</div>
 
    </div>
 
  </div>
 
</%def>
 

	
 

	
 
<%def name="comment_inline_form(changeset)">
 
<div id='comment-inline-form-template' style="display:none">
 
  <div class="comment-inline-form">
 
  %if c.rhodecode_user.username != 'default':
 
      ${h.form(h.url('changeset_comment', repo_name=c.repo_name, revision=changeset.raw_id))}
 
    <div class="overlay"><div class="overlay-text">${_('Submitting...')}</div></div>
 
      ${h.form(h.url('changeset_comment', repo_name=c.repo_name, revision=changeset.raw_id),class_='inline-form')}
 
      <div class="clearfix">
 
          <div class="comment-help">${_('Commenting on line')} {1}. ${_('Comments parsed using')}
 
          <a href="${h.url('rst_help')}">RST</a> ${_('syntax')} ${_('with')}
 
          <span style="color:#003367" class="tooltip" title="${_('Use @username inside this text to send notification to this RhodeCode user')}">@mention</span> ${_('support')}
 
          </div>
 
          <textarea id="text_{1}" name="text"></textarea>
 
      </div>
 
      <div class="comment-button">
 
      <input type="hidden" name="f_path" value="{0}">
 
      <input type="hidden" name="line" value="{1}">
 
      ${h.submit('save', _('Comment'), class_='ui-btn')}
 
      ${h.submit('save', _('Comment'), class_='ui-btn save-inline-form')}
 
      ${h.reset('hide-inline-form', _('Hide'), class_='ui-btn hide-inline-form')}
 
      </div>
 
      ${h.end_form()}
 
  %else:
 
      ${h.form('')}
 
      <div class="clearfix">
 
          <div class="comment-help">
 
            ${'You need to be logged in to comment.'} <a href="${h.url('login_home',came_from=h.url.current())}">${_('Login now')}</a>
 
          </div>
 
      </div>
 
      <div class="comment-button">
 
      ${h.reset('hide-inline-form', _('Hide'), class_='ui-btn hide-inline-form')}
 
      </div>
 
      ${h.end_form()}
 
  %endif
 
  </div>
 
</div>
 
</%def>
 

	
 

	
 
<%def name="comments(changeset)">
 

	
 
<div class="comments">
 
<%def name="inlines(changeset)">
 
    <div class="comments-number">${len(c.comments)} comment(s) (${c.inline_cnt} ${_('inline')})</div>
 

	
 
    %for path, lines in c.inline_comments:
 
        <div style="display:none" class="inline-comment-placeholder" path="${path}" target_id="${h.FID(changeset.raw_id,path)}">
 
        % for line,comments in lines.iteritems():
 
            <div class="inline-comment-placeholder-line" line="${line}" target_id="${h.safeid(h.safe_unicode(path))}">
 
            <div style="display:none" class="inline-comment-placeholder" path="${path}" target_id="${h.safeid(h.safe_unicode(path))}">
 
            %for co in comments:
 
                ${comment_block(co)}
 
            %endfor
 
            </div>
 
        %endfor
 
        </div>
 
    %endfor
 

	
 
</%def>
 

	
 
<%def name="comments(changeset)">
 

	
 
<div class="comments">
 
    <div id="inline-comments-container">
 
     ${inlines(changeset)}
 
    </div>
 

	
 
    %for co in c.comments:
 
        <div id="comment-tr-${co.comment_id}">
 
        ${comment_block(co)}
 
        </div>
 
    %endfor
 
    %if c.rhodecode_user.username != 'default':
 
    <div class="comment-form">
 
        ${h.form(h.url('changeset_comment', repo_name=c.repo_name, revision=changeset.raw_id))}
 
        <strong>${_('Leave a comment')}</strong>
 
        <div class="clearfix">
 
            <div class="comment-help">
 
                ${_('Comments parsed using')} <a href="${h.url('rst_help')}">RST</a> ${_('syntax')}
 
                ${_('with')} <span style="color:#003367" class="tooltip" title="${_('Use @username inside this text to send notification to this RhodeCode user')}">@mention</span> ${_('support')}
 
            </div>
 
                ${h.textarea('text')}
 
        </div>
 
        <div class="comment-button">
 
        ${h.submit('save', _('Comment'), class_='ui-button')}
 
        </div>
 
        ${h.end_form()}
 
    </div>
 
    %endif
 
</div>
 
</%def>
rhodecode/templates/files/files_annotate.html
Show inline comments
 
deleted file
rhodecode/templates/files/files_browser.html
Show inline comments
 
<%def name="file_class(node)">
 
	%if node.is_file():
 
		<%return "browser-file" %>
 
	%else:
 
		<%return "browser-dir"%>
 
	%endif
 
</%def>
 
<div id="body" class="browserblock">
 
    <div class="browser-header">
 
		<div class="browser-nav">
 
			${h.form(h.url.current())}
 
			<div class="info_box">
 
	          <span class="rev">${_('view')}@rev</span>
 
	          <a class="ui-btn" href="${c.url_prev}" title="${_('previous revision')}">&laquo;</a>
 
	          ${h.text('at_rev',value=c.changeset.revision,size=5)}
 
	          <a class="ui-btn" href="${c.url_next}" title="${_('next revision')}">&raquo;</a>
 
	          ## ${h.submit('view',_('view'),class_="ui-btn")}
 
		    </div>
 
			${h.end_form()}
 
		</div>
 
	    <div class="browser-branch">
 
	       ${h.checkbox('stay_at_branch',c.changeset.branch,c.changeset.branch==c.branch)}
 
	       <label>${_('follow current branch')}</label>
 
	    </div>
 
        <div class="browser-search">
 
              <div id="search_activate_id" class="search_activate">
 
                  <a class="ui-btn" id="filter_activate" href="#">${_('search file list')}</a>
 
              </div>
 
              % if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
 
                    <div id="add_node_id" class="add_node">
 
                        <a class="ui-btn" href="${h.url('files_add_home',repo_name=c.repo_name,revision=c.changeset.raw_id,f_path=c.f_path)}">${_('add new file')}</a>
 
                    </div>
 
              % endif
 
        <div>
 
            <div id="node_filter_box_loading" style="display:none">${_('Loading file list...')}</div>
 
            <div id="node_filter_box" style="display:none">
 
            ${h.files_breadcrumbs(c.repo_name,c.changeset.raw_id,c.file.path)}/<input class="init" type="text" value="type to search..." name="filter" size="25" id="node_filter" autocomplete="off">
 
            </div>
 
        </div>
 
        </div>
 
    </div>
 

	
 
	<div class="browser-body">
 
		<table class="code-browser">
 
		         <thead>
 
		             <tr>
 
		                 <th>${_('Name')}</th>
 
		                 <th>${_('Size')}</th>
 
		                 <th>${_('Mimetype')}</th>
 
		                 <th>${_('Last Revision')}</th>
 
		                 <th>${_('Last modified')}</th>
 
		                 <th>${_('Last commiter')}</th>
 
		             </tr>
 
		         </thead>
 

	
 
                <tbody id="tbody">
 
          		%if c.file.parent:
 
         		<tr class="parity0">
 
	          		<td>
 
	          			${h.link_to('..',h.url('files_home',repo_name=c.repo_name,revision=c.changeset.raw_id,f_path=c.file.parent.path),class_="browser-dir ypjax-link")}
 
	          		</td>
 
	          		<td></td>
 
	          		<td></td>
 
	          		<td></td>
 
	          		<td></td>
 
	          		<td></td>
 
				</tr>
 
          		%endif
 

	
 
		    %for cnt,node in enumerate(c.file):
 
				<tr class="parity${cnt%2}">
 
		             <td>
 
                        %if node.is_submodule():
 
                           ${h.link_to(node.name,node.url or '#',class_="submodule-dir ypjax-link")}
 
                        %else:
 
                        ${h.link_to(node.name,h.url('files_home',repo_name=c.repo_name,revision=c.changeset.raw_id,f_path=h.safe_unicode(node.path)),class_=file_class(node)+" ypjax-link")}
 
                        %endif:
 
		             </td>
 
		             <td>
 
		             %if node.is_file():
 
		             	${h.format_byte_size(node.size,binary=True)}
 
		             %endif
 
		             </td>
 
		             <td>
 
		              %if node.is_file():
 
		                  ${node.mimetype}
 
		              %endif
 
		             </td>
 
		             <td>
 
		             	%if node.is_file():
 
		             		<div class="tooltip" title="${node.last_changeset.message}">
 
		             		<pre>${'r%s:%s' % (node.last_changeset.revision,node.last_changeset.short_id)}</pre>
 
                            </div>
 
		             	%endif
 
		             </td>
 
		             <td>
 
		             	%if node.is_file():
 
		             		<span class="tooltip" title="${node.last_changeset.date}">
 
                            ${h.age(node.last_changeset.date)}</span>
 
		             	%endif
 
		             </td>
 
		             <td>
 
		             	%if node.is_file():
 
		             		<span title="${node.last_changeset.author}">
 
                            ${h.person(node.last_changeset.author)}
 
                            </span>
 
		             	%endif
 
		             </td>
 
				</tr>
 
			%endfor
 
                </tbody>
 
                <tbody id="tbody_filtered" style="display:none">
 
                </tbody>
 
		</table>
 
	</div>
 
</div>
rhodecode/templates/files/files_edit.html
Show inline comments
 
<%inherit file="/base/base.html"/>
 

	
 
<%def name="title()">
 
    ${c.repo_name} ${_('Edit file')} - ${c.rhodecode_name}
 
</%def>
 

	
 
<%def name="js_extra()">
 
<script type="text/javascript" src="${h.url('/js/codemirror.js')}"></script>
 
</%def>
 
<%def name="css_extra()">
 
<link rel="stylesheet" type="text/css" href="${h.url('/css/codemirror.css')}"/>
 
</%def>
 

	
 
<%def name="breadcrumbs_links()">
 
    ${h.link_to(u'Home',h.url('/'))}
 
    &raquo;
 
    ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
 
    &raquo;
 
    ${_('edit file')} @ R${c.cs.revision}:${h.short_id(c.cs.raw_id)}
 
</%def>
 

	
 
<%def name="page_nav()">
 
		${self.menu('files')}
 
</%def>
 
<%def name="main()">
 
<div class="box">
 
    <!-- box / title -->
 
    <div class="title">
 
        ${self.breadcrumbs()}
 
        <ul class="links">
 
            <li>
 
              <span style="text-transform: uppercase;">
 
              <a href="#">${_('branch')}: ${c.cs.branch}</a></span>
 
            </li>
 
        </ul>
 
    </div>
 
    <div class="table">
 
		<div id="files_data">
 
			<h3 class="files_location">${_('Location')}: ${h.files_breadcrumbs(c.repo_name,c.cs.revision,c.file.path)}</h3>
 
			${h.form(h.url.current(),method='post',id='eform')}
 
			<div id="body" class="codeblock">
 
            <div class="code-header">
 
                <div class="stats">
 
                    <div class="left"><img src="${h.url('/images/icons/file.png')}"/></div>
 
                    <div class="left item">${h.link_to("r%s:%s" % (c.file.changeset.revision,h.short_id(c.file.changeset.raw_id)),h.url('changeset_home',repo_name=c.repo_name,revision=c.file.changeset.raw_id))}</div>
 
                    <div class="left item">${h.format_byte_size(c.file.size,binary=True)}</div>
 
                    <div class="left item last">${c.file.mimetype}</div>
 
                    <div class="buttons">
 
                      ${h.link_to(_('show annotation'),h.url('files_annotate_home',repo_name=c.repo_name,revision=c.cs.raw_id,f_path=c.f_path),class_="ui-btn")}
 
                      ${h.link_to(_('show as raw'),h.url('files_raw_home',repo_name=c.repo_name,revision=c.cs.raw_id,f_path=c.f_path),class_="ui-btn")}
 
                      ${h.link_to(_('download as raw'),h.url('files_rawfile_home',repo_name=c.repo_name,revision=c.cs.raw_id,f_path=c.f_path),class_="ui-btn")}
 
                      % if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
 
                       % if not c.file.is_binary:
 
                        ${h.link_to(_('source'),h.url('files_home',repo_name=c.repo_name,revision=c.cs.raw_id,f_path=c.f_path),class_="ui-btn")}
 
                       % endif
 
                      % endif
 
                    </div>
 
                </div>
 
                <div class="commit">${_('Editing file')}: ${c.file.path}</div>
 
                <div class="commit">${_('Editing file')}: ${c.file.unicode_path}</div>
 
            </div>
 
			    <pre id="editor_pre"></pre>
 
				<textarea id="editor" name="content" style="display:none">${h.escape(c.file.content)|n}</textarea>
 
				<div style="padding: 10px;color:#666666">${_('commit message')}</div>
 
				<textarea id="commit" name="message" style="height: 60px;width: 99%;margin-left:4px"></textarea>
 
			</div>
 
			<div style="text-align: left;padding-top: 5px">
 
            ${h.submit('commit',_('Commit changes'),class_="ui-btn")}
 
            ${h.reset('reset',_('Reset'),class_="ui-btn")}
 
			</div>
 
			${h.end_form()}
 
			<script type="text/javascript">
 
			var reset_url = "${h.url('files_home',repo_name=c.repo_name,revision=c.cs.raw_id,f_path=c.file.path)}";
 
			initCodeMirror('editor',reset_url);
 
			</script>
 
		</div>
 
    </div>
 
</div>
 
</%def>
rhodecode/templates/files/files_source.html
Show inline comments
 
<dl>
 
	<dt style="padding-top:10px;font-size:16px">${_('History')}</dt>
 
	<dd>
 
		<div>
 
		${h.form(h.url('files_diff_home',repo_name=c.repo_name,f_path=c.f_path),method='get')}
 
		${h.hidden('diff2',c.file.changeset.raw_id)}
 
		${h.select('diff1',c.file.changeset.raw_id,c.file_history)}
 
		${h.submit('diff','diff to revision',class_="ui-btn")}
 
		${h.submit('show_rev','show at revision',class_="ui-btn")}
 
		${h.end_form()}
 
		</div>
 
	</dd>
 
</dl>
 

	
 
<div id="body" class="codeblock">
 
	<div class="code-header">
 
        <div class="stats">
 
            <div class="left img"><img src="${h.url('/images/icons/file.png')}"/></div>
 
            <div class="left item"><pre>${h.link_to("r%s:%s" % (c.file.changeset.revision,h.short_id(c.file.changeset.raw_id)),h.url('changeset_home',repo_name=c.repo_name,revision=c.file.changeset.raw_id))}</pre></div>
 
            <div class="left item"><pre class="tooltip" title="${c.file.changeset.date}">${h.link_to("r%s:%s" % (c.file.changeset.revision,h.short_id(c.file.changeset.raw_id)),h.url('changeset_home',repo_name=c.repo_name,revision=c.file.changeset.raw_id))}</pre></div>
 
            <div class="left item"><pre>${h.format_byte_size(c.file.size,binary=True)}</pre></div>
 
            <div class="left item last"><pre>${c.file.mimetype}</pre></div>
 
            <div class="buttons">
 
              %if c.annotate:
 
                ${h.link_to(_('show source'),    h.url('files_home',         repo_name=c.repo_name,revision=c.file.changeset.raw_id,f_path=c.f_path),class_="ui-btn")}
 
              %else:
 
              ${h.link_to(_('show annotation'),h.url('files_annotate_home',repo_name=c.repo_name,revision=c.file.changeset.raw_id,f_path=c.f_path),class_="ui-btn")}
 
              %endif
 
              ${h.link_to(_('show as raw'),h.url('files_raw_home',repo_name=c.repo_name,revision=c.file.changeset.raw_id,f_path=c.f_path),class_="ui-btn")}
 
              ${h.link_to(_('download as raw'),h.url('files_rawfile_home',repo_name=c.repo_name,revision=c.file.changeset.raw_id,f_path=c.f_path),class_="ui-btn")}
 
              % if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
 
               % if not c.file.is_binary:
 
                ${h.link_to(_('edit'),h.url('files_edit_home',repo_name=c.repo_name,revision=c.file.changeset.raw_id,f_path=c.f_path),class_="ui-btn")}
 
               % endif
 
              % endif
 
            </div>
 
        </div>
 
        <div class="author">
 
            <div class="gravatar">
 
                <img alt="gravatar" src="${h.gravatar_url(h.email(c.file.changeset.author),16)}"/>
 
            </div>
 
            <div title="${c.file.changeset.author}" class="user">${h.person(c.file.changeset.author)}</div>
 
        </div>
 
		<div class="commit">${h.urlify_commit(c.file.changeset.message,c.repo_name)}</div>
 
	</div>
 
	<div class="code-body">
 
	   %if c.file.is_binary:
 
	       ${_('Binary file (%s)') % c.file.mimetype}
 
	   %else:
 
		% if c.file.size < c.cut_off_limit:
 
            %if c.annotate:
 
              ${h.pygmentize_annotation(c.repo_name,c.file,linenos=True,anchorlinenos=True,lineanchors='L',cssclass="code-highlight")}
 
            %else:
 
			${h.pygmentize(c.file,linenos=True,anchorlinenos=True,lineanchors='L',cssclass="code-highlight")}
 
            %endif
 
		%else:
 
			${_('File is too big to display')} ${h.link_to(_('show as raw'),
 
			h.url('files_raw_home',repo_name=c.repo_name,revision=c.file.changeset.raw_id,f_path=c.f_path))}
 
		%endif
 
     %endif
 
	</div>
 
</div>
 

	
 
       <script type="text/javascript">
 
YUE.onDOMReady(function(){
 
           function highlight_lines(lines){
 
               for(pos in lines){
 
                 YUD.setStyle('L'+lines[pos],'background-color','#FFFFBE');
 
               }
 
           }
 
           page_highlights = location.href.substring(location.href.indexOf('#')+1).split('L');
 
           if (page_highlights.length == 2){
 
              highlight_ranges  = page_highlights[1].split(",");
 

	
 
              var h_lines = [];
 
              for (pos in highlight_ranges){
 
                   var _range = highlight_ranges[pos].split('-');
 
                   if(_range.length == 2){
 
                       var start = parseInt(_range[0]);
 
                       var end = parseInt(_range[1]);
 
                       if (start < end){
 
                           for(var i=start;i<=end;i++){
 
                               h_lines.push(i);
 
                           }
 
                       }
 
                   }
 
                   else{
 
                       h_lines.push(parseInt(highlight_ranges[pos]));
 
                   }
 
             }
 
           highlight_lines(h_lines);
 

	
 
           //remember original location
 
           var old_hash  = location.href.substring(location.href.indexOf('#'));
 

	
 
           // this makes a jump to anchor moved by 3 posstions for padding
 
           window.location.hash = '#L'+Math.max(parseInt(h_lines[0])-3,1);
 

	
 
           //sets old anchor
 
           window.location.hash = old_hash;
 

	
 
           }
 
       </script>
 
     %endif
 
	</div>
 
</div>
 

	
 
<script type="text/javascript">
 
YUE.onDOMReady(function(){
 
    YUE.on('show_rev','click',function(e){
 
    	YUE.preventDefault(e);
 
        var cs = YUD.get('diff1').value;
 
        %if c.annotate:
 
          var url = "${h.url('files_annotate_home',repo_name=c.repo_name,revision='__CS__',f_path=c.f_path)}".replace('__CS__',cs);
 
        %else:
 
        var url = "${h.url('files_home',repo_name=c.repo_name,revision='__CS__',f_path=c.f_path)}".replace('__CS__',cs);
 
        %endif
 
        window.location = url;
 
    });
 
    YUE.on('hlcode','mouseup',getSelectionLink("${_('Selection link')}"))
 
   });
 
</script>
rhodecode/templates/files/files_ypjax.html
Show inline comments
 
%if c.file:
 
    <h3 class="files_location">
 
        ${_('Location')}: ${h.files_breadcrumbs(c.repo_name,c.changeset.raw_id,c.file.path)}
 
        %if c.annotate:
 
        - ${_('annotation')}
 
        %endif
 
    </h3>
 
        %if c.file.is_dir():
 
            <%include file='files_browser.html'/>
 
        %else:
 
            <%include file='files_source.html'/>
 
        %endif
 
%else:
 
    <h2>
 
        <a href="#" onClick="javascript:parent.history.back();" target="main">${_('Go back')}</a>
 
        ${_('No files at given path')}: "${c.f_path or "/"}"
 
    </h2>
 
%endif
rhodecode/templates/forks/fork.html
Show inline comments
 
## -*- coding: utf-8 -*-
 
<%inherit file="/base/base.html"/>
 

	
 
<%def name="title()">
 
    ${c.repo_name} ${_('Fork')} - ${c.rhodecode_name}
 
</%def>
 

	
 
<%def name="breadcrumbs_links()">
 
    ${h.link_to(u'Home',h.url('/'))}
 
    &raquo;
 
    ${h.link_to(c.repo_info.repo_name,h.url('summary_home',repo_name=c.repo_info.repo_name))}
 
    &raquo;
 
    ${_('fork')}
 
</%def>
 

	
 
<%def name="page_nav()">
 
	${self.menu('')}
 
</%def>
 
<%def name="main()">
 
<div class="box">
 
    <!-- box / title -->
 
    <div class="title">
 
        ${self.breadcrumbs()}
 
    </div>
 
    ${h.form(url('repo_fork_create_home',repo_name=c.repo_info.repo_name))}
 
    <div class="form">
 
        <!-- fields -->
 
        <div class="fields">
 
            <div class="field">
 
              <div class="label">
 
                  <label for="repo_name">${_('Fork name')}:</label>
 
              </div>
 
              <div class="input">
 
                  ${h.text('repo_name',class_="small")}
 
                  ${h.hidden('repo_type',c.repo_info.repo_type)}
 
                  ${h.hidden('fork_parent_id',c.repo_info.repo_id)}
 
              </div>
 
            </div>
 
            <div class="field">
 
                 <div class="label">
 
                     <label for="repo_group">${_('Repository group')}:</label>
 
                 </div>
 
                 <div class="input">
 
                     ${h.select('repo_group','',c.repo_groups,class_="medium")}
 
                 </div>
 
            </div>
 
            <div class="field">
 
                <div class="label label-textarea">
 
                    <label for="description">${_('Description')}:</label>
 
                </div>
 
                <div class="textarea text-area editor">
 
                    ${h.textarea('description',cols=23,rows=5)}
 
                </div>
 
             </div>
 
            <div class="field">
 
                <div class="label label-checkbox">
 
                    <label for="private">${_('Private')}:</label>
 
                </div>
 
                <div class="checkboxes">
 
                    ${h.checkbox('private',value="True")}
 
                </div>
 
            </div>
 
            <div class="field">
 
                <div class="label label-checkbox">
 
                    <label for="private">${_('Copy permissions')}:</label>
 
                </div>
 
                <div class="checkboxes">
 
                    ${h.checkbox('copy_permissions',value="True")}
 
                    ${h.checkbox('copy_permissions',value="True", checked="checked")}
 
                </div>
 
             </div>
 
            <div class="field">
 
                <div class="label label-checkbox">
 
                    <label for="private">${_('Update after clone')}:</label>
 
                </div>
 
                <div class="checkboxes">
 
                    ${h.checkbox('update_after_clone',value="True")}
 
                </div>
 
             </div>
 
	        <div class="buttons">
 
	          ${h.submit('',_('fork this repository'),class_="ui-button")}
 
	        </div>
 
        </div>
 
    </div>
 
    ${h.end_form()}
 
</div>
 
</%def>
rhodecode/templates/index_base.html
Show inline comments
 
@@ -26,176 +26,176 @@
 
              <table id="groups_list">
 
                  <thead>
 
                      <tr>
 
                          <th class="left"><a href="#">${_('Group name')}</a></th>
 
                          <th class="left"><a href="#">${_('Description')}</a></th>
 
                          ##<th class="left"><a href="#">${_('Number of repositories')}</a></th>
 
                      </tr>
 
                  </thead>
 

	
 
                  ## REPO GROUPS
 
                  % for gr in c.groups:
 
                    <tr>
 
                        <td>
 
                            <div style="white-space: nowrap">
 
                            <img class="icon" alt="${_('Repositories group')}" src="${h.url('/images/icons/database_link.png')}"/>
 
                            ${h.link_to(gr.name,url('repos_group_home',group_name=gr.group_name))}
 
                            </div>
 
                        </td>
 
                        <td>${gr.group_description}</td>
 
                        ## this is commented out since for multi nested repos can be HEAVY!
 
                        ## in number of executed queries during traversing uncomment at will
 
                        ##<td><b>${gr.repositories_recursive_count}</b></td>
 
                    </tr>
 
                  % endfor
 

	
 
              </table>
 
            </div>
 
            <div style="height: 20px"></div>
 
            % endif
 
            <div id="welcome" style="display:none;text-align:center">
 
                <h1><a href="${h.url('home')}">${c.rhodecode_name} ${c.rhodecode_version}</a></h1>
 
            </div>
 
            <div id='repos_list_wrap' class="yui-skin-sam">
 
            <%cnt=0%>
 
            <%namespace name="dt" file="/_data_table/_dt_elements.html"/>
 

	
 
            <table id="repos_list">
 
            <thead>
 
                <tr>
 
                    <th class="left"></th>
 
                    <th class="left">${_('Name')}</th>
 
                    <th class="left">${_('Description')}</th>
 
                    <th class="left">${_('Last change')}</th>
 
                    <th class="left">${_('Tip')}</th>
 
                    <th class="left">${_('Owner')}</th>
 
                    <th class="left">${_('RSS')}</th>
 
                    <th class="left">${_('Atom')}</th>
 
                </tr>
 
            </thead>
 
            <tbody>
 
            %for cnt,repo in enumerate(c.repos_list):
 
                <tr class="parity${(cnt+1)%2}">
 
                    ##QUICK MENU
 
                    <td class="quick_repo_menu">
 
                      ${dt.quick_menu(repo['name'])}
 
                    </td>
 
                    ##REPO NAME AND ICONS
 
                    <td class="reponame">
 
                      ${dt.repo_name(repo['name'],repo['dbrepo']['repo_type'],repo['dbrepo']['private'],repo['dbrepo_fork'].get('repo_name'),pageargs.get('short_repo_names'))}
 
                    </td>
 
                    ##DESCRIPTION
 
                    <td><span class="tooltip" title="${h.tooltip(repo['description'])}">
 
                       ${h.truncate(repo['description'],60)}</span>
 
                    </td>
 
                    ##LAST CHANGE DATE
 
                    <td>
 
                      <span class="tooltip" title="${repo['last_change']}">${h.age(repo['last_change'])}</span>
 
                    </td>
 
                    ##LAST REVISION
 
                    <td>
 
                        ${dt.revision(repo['name'],repo['rev'],repo['tip'],repo['author'],repo['last_msg'])}
 
                    </td>
 
                    ##
 
                    <td title="${repo['contact']}">${h.person(repo['contact'])}</td>
 
                    <td>
 
                      %if c.rhodecode_user.username != 'default':
 
                        <a title="${_('Subscribe to %s rss feed')%repo['name']}" class="rss_icon"  href="${h.url('rss_feed_home',repo_name=repo['name'],api_key=c.rhodecode_user.api_key)}"></a>
 
                      %else:
 
                        <a title="${_('Subscribe to %s rss feed')%repo['name']}" class="rss_icon"  href="${h.url('rss_feed_home',repo_name=repo['name'])}"></a>
 
                      %endif:
 
                    </td>
 
                    <td>
 
                      %if c.rhodecode_user.username != 'default':
 
                        <a title="${_('Subscribe to %s atom feed')%repo['name']}"  class="atom_icon" href="${h.url('atom_feed_home',repo_name=repo['name'],api_key=c.rhodecode_user.api_key)}"></a>
 
                      %else:
 
                        <a title="${_('Subscribe to %s atom feed')%repo['name']}"  class="atom_icon" href="${h.url('atom_feed_home',repo_name=repo['name'])}"></a>
 
                      %endif:
 
                    </td>
 
                </tr>
 
            %endfor
 
            </tbody>
 
            </table>
 
            </div>
 
        </div>
 
    </div>
 
    <script>
 
      YUD.get('repo_count').innerHTML = ${cnt};
 
      YUD.get('repo_count').innerHTML = ${cnt+1};
 
      var func = function(node){
 
          return node.parentNode.parentNode.parentNode.parentNode;
 
      }
 

	
 

	
 
      // groups table sorting
 
      var myColumnDefs = [
 
          {key:"name",label:"${_('Group Name')}",sortable:true,
 
              sortOptions: { sortFunction: groupNameSort }},
 
          {key:"desc",label:"${_('Description')}",sortable:true},
 
      ];
 

	
 
      var myDataSource = new YAHOO.util.DataSource(YUD.get("groups_list"));
 

	
 
      myDataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
 
      myDataSource.responseSchema = {
 
          fields: [
 
              {key:"name"},
 
              {key:"desc"},
 
          ]
 
      };
 

	
 
      var myDataTable = new YAHOO.widget.DataTable("groups_list_wrap", myColumnDefs, myDataSource,
 
              {
 
               sortedBy:{key:"name",dir:"asc"},
 
               MSG_SORTASC:"${_('Click to sort ascending')}",
 
               MSG_SORTDESC:"${_('Click to sort descending')}"
 
              }
 
      );
 

	
 
      // main table sorting
 
      var myColumnDefs = [
 
          {key:"menu",label:"",sortable:false,className:"quick_repo_menu hidden"},
 
          {key:"name",label:"${_('Name')}",sortable:true,
 
              sortOptions: { sortFunction: nameSort }},
 
          {key:"desc",label:"${_('Description')}",sortable:true},
 
          {key:"last_change",label:"${_('Last Change')}",sortable:true,
 
              sortOptions: { sortFunction: ageSort }},
 
          {key:"tip",label:"${_('Tip')}",sortable:true,
 
        	  sortOptions: { sortFunction: revisionSort }},
 
          {key:"owner",label:"${_('Owner')}",sortable:true},
 
          {key:"rss",label:"",sortable:false},
 
          {key:"atom",label:"",sortable:false},
 
      ];
 

	
 
      var myDataSource = new YAHOO.util.DataSource(YUD.get("repos_list"));
 

	
 
      myDataSource.responseType = YAHOO.util.DataSource.TYPE_HTMLTABLE;
 

	
 
      myDataSource.responseSchema = {
 
          fields: [
 
              {key:"menu"},
 
              {key:"name"},
 
              {key:"desc"},
 
              {key:"last_change"},
 
              {key:"tip"},
 
              {key:"owner"},
 
              {key:"rss"},
 
              {key:"atom"},
 
          ]
 
      };
 

	
 
      var myDataTable = new YAHOO.widget.DataTable("repos_list_wrap", myColumnDefs, myDataSource,
 
              {
 
               sortedBy:{key:"name",dir:"asc"},
 
               MSG_SORTASC:"${_('Click to sort ascending')}",
 
               MSG_SORTDESC:"${_('Click to sort descending')}",
 
               MSG_EMPTY:"${_('No records found.')}",
 
               MSG_ERROR:"${_('Data error.')}",
 
               MSG_LOADING:"${_('Loading...')}",
 
              }
 
      );
 
      myDataTable.subscribe('postRenderEvent',function(oArgs) {
 
          tooltip_activate();
 
          quick_repo_menu();
 
          q_filter('q_filter',YUQ('div.table tr td a.repo_name'),func);
 
      });
 

	
 
    </script>
rhodecode/templates/shortlog/shortlog_data.html
Show inline comments
 
## -*- coding: utf-8 -*-
 
%if c.repo_changesets:
 
<table class="table_disp">
 
	<tr>
 
	    <th class="left">${_('revision')}</th>
 
        <th class="left">${_('commit message')}</th>
 
		<th class="left">${_('age')}</th>
 
		<th class="left">${_('author')}</th>
 
		<th class="left">${_('branch')}</th>
 
		<th class="left">${_('tags')}</th>
 
	</tr>
 
%for cnt,cs in enumerate(c.repo_changesets):
 
	<tr class="parity${cnt%2}">
 
        <td>
 
            <div><pre><a href="${h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id)}">r${cs.revision}:${h.short_id(cs.raw_id)}</a></pre></div>
 
        </td>
 
        <td>
 
            ${h.link_to(h.truncate(cs.message,50),
 
            ${h.link_to(h.truncate(cs.message,50) or _('No commit message'),
 
            h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id),
 
            title=cs.message)}
 
        </td>
 
        <td><span class="tooltip" title="${cs.date}">
 
                      ${h.age(cs.date)}</span>
 
        </td>
 
		<td title="${cs.author}">${h.person(cs.author)}</td>
 
		<td>
 
			<span class="logtags">
 
                %if cs.branch:
 
				<span class="branchtag">
 
                %if h.is_hg(c.rhodecode_repo):
 
                    ${cs.branch}
 
                </span>
 
                %endif
 
                </span>
 
			</span>
 
		</td>
 
		<td>
 
			<span class="logtags">
 
				%for tag in cs.tags:
 
					<span class="tagtag">${tag}</span>
 
				%endfor
 
			</span>
 
		</td>
 
	</tr>
 
%endfor
 

	
 
</table>
 

	
 
<script type="text/javascript">
 
  YUE.onDOMReady(function(){
 
    YUE.delegate("shortlog_data","click",function(e, matchedEl, container){
 
        ypjax(e.target.href,"shortlog_data",function(){tooltip_activate();});
 
        YUE.preventDefault(e);
 
    },'.pager_link');
 
  });
 
</script>
 

	
 
<div class="pagination-wh pagination-left">
 
${c.repo_changesets.pager('$link_previous ~2~ $link_next')}
 
</div>
 
%else:
 

	
 
%if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
 
<h4>${_('Add or upload files directly via RhodeCode')}</h4>
 
<div style="margin: 20px 30px;">
 
  <div id="add_node_id" class="add_node">
 
      <a class="ui-btn" href="${h.url('files_add_home',repo_name=c.repo_name,revision=0,f_path='')}">${_('add new file')}</a>
 
  </div>
 
</div>
 
%endif
 

	
 

	
 
<h4>${_('Push new repo')}</h4>
 
<pre>
 
    ${c.rhodecode_repo.alias} clone ${c.clone_repo_url}
 
    ${c.rhodecode_repo.alias} add README # add first file
 
    ${c.rhodecode_repo.alias} commit -m "Initial" # commit with message
 
    ${c.rhodecode_repo.alias} push # push changes back
 
    ${c.rhodecode_repo.alias} push ${'origin master' if h.is_git(c.rhodecode_repo) else ''} # push changes back
 
</pre>
 

	
 
<h4>${_('Existing repository?')}</h4>
 
<pre>
 
    ${c.rhodecode_repo.alias} push ${c.clone_repo_url}
 
</pre>
 
%endif
rhodecode/tests/__init__.py
Show inline comments
 
"""Pylons application test package
 

	
 
This package assumes the Pylons environment is already loaded, such as
 
when this script is imported from the `nosetests --with-pylons=test.ini`
 
command.
 

	
 
This module initializes the application via ``websetup`` (`paster
 
setup-app`) and provides the base testing objects.
 
"""
 
import os
 
import time
 
import logging
 
from os.path import join as jn
 

	
 
from unittest import TestCase
 
from tempfile import _RandomNameSequence
 

	
 
from paste.deploy import loadapp
 
from paste.script.appinstall import SetupCommand
 
from pylons import config, url
 
from routes.util import URLGenerator
 
from webtest import TestApp
 

	
 
from rhodecode import is_windows
 
from rhodecode.model.meta import Session
 
from rhodecode.model.db import User
 

	
 
import pylons.test
 

	
 
os.environ['TZ'] = 'UTC'
 
if not is_windows:
 
time.tzset()
 

	
 
log = logging.getLogger(__name__)
 

	
 
__all__ = [
 
    'environ', 'url', 'TestController', 'TESTS_TMP_PATH', 'HG_REPO',
 
    'GIT_REPO', 'NEW_HG_REPO', 'NEW_GIT_REPO', 'HG_FORK', 'GIT_FORK',
 
    'TEST_USER_ADMIN_LOGIN', 'TEST_USER_REGULAR_LOGIN', 'TEST_USER_REGULAR_PASS',
 
    'TEST_USER_REGULAR_EMAIL', 'TEST_USER_REGULAR2_LOGIN',
 
    'TEST_USER_REGULAR2_PASS', 'TEST_USER_REGULAR2_EMAIL'
 
]
 

	
 
# Invoke websetup with the current config file
 
# SetupCommand('setup-app').run([config_file])
 

	
 
##RUNNING DESIRED TESTS
 
# nosetests -x rhodecode.tests.functional.test_admin_settings:TestSettingsController.test_my_account
 
# nosetests --pdb --pdb-failures
 
environ = {}
 

	
 
#SOME GLOBALS FOR TESTS
 

	
 
TESTS_TMP_PATH = jn('/', 'tmp', 'rc_test_%s' % _RandomNameSequence().next())
 
TEST_USER_ADMIN_LOGIN = 'test_admin'
 
TEST_USER_ADMIN_PASS = 'test12'
 
TEST_USER_ADMIN_EMAIL = 'test_admin@mail.com'
 

	
 
TEST_USER_REGULAR_LOGIN = 'test_regular'
 
TEST_USER_REGULAR_PASS = 'test12'
 
TEST_USER_REGULAR_EMAIL = 'test_regular@mail.com'
 

	
 
TEST_USER_REGULAR2_LOGIN = 'test_regular2'
 
TEST_USER_REGULAR2_PASS = 'test12'
 
TEST_USER_REGULAR2_EMAIL = 'test_regular2@mail.com'
 

	
 
HG_REPO = 'vcs_test_hg'
 
GIT_REPO = 'vcs_test_git'
 

	
 
NEW_HG_REPO = 'vcs_test_hg_new'
 
NEW_GIT_REPO = 'vcs_test_git_new'
 

	
 
HG_FORK = 'vcs_test_hg_fork'
 
GIT_FORK = 'vcs_test_git_fork'
 

	
 

	
 
class TestController(TestCase):
 

	
 
    def __init__(self, *args, **kwargs):
 
        wsgiapp = pylons.test.pylonsapp
 
        config = wsgiapp.config
 

	
 
        self.app = TestApp(wsgiapp)
 
        url._push_object(URLGenerator(config['routes.map'], environ))
 
        self.Session = Session
 
        self.index_location = config['app_conf']['index_dir']
 
        TestCase.__init__(self, *args, **kwargs)
 

	
 
    def log_user(self, username=TEST_USER_ADMIN_LOGIN,
 
                 password=TEST_USER_ADMIN_PASS):
 
        self._logged_username = username
 
        response = self.app.post(url(controller='login', action='index'),
 
                                 {'username':username,
 
                                  'password':password})
 

	
 
        if 'invalid user name' in response.body:
 
            self.fail('could not login using %s %s' % (username, password))
 

	
 
        self.assertEqual(response.status, '302 Found')
 
        ses = response.session['rhodecode_user']
 
        self.assertEqual(ses.get('username'), username)
 
        response = response.follow()
 
        self.assertEqual(ses.get('is_authenticated'), True)
 

	
 
        return response.session['rhodecode_user']
 

	
 
    def _get_logged_user(self):
 
        return User.get_by_username(self._logged_username)
 

	
 
    def checkSessionFlash(self, response, msg):
 
        self.assertTrue('flash' in response.session)
 
        self.assertTrue(msg in response.session['flash'][0][1])
rhodecode/tests/functional/test_changeset_comments.py
Show inline comments
 
from rhodecode.tests import *
 
from rhodecode.model.db import ChangesetComment, Notification, User, \
 
    UserNotification
 

	
 

	
 
class TestChangeSetCommentsController(TestController):
 

	
 
    def setUp(self):
 
        for x in ChangesetComment.query().all():
 
            self.Session.delete(x)
 
        self.Session.commit()
 

	
 
        for x in Notification.query().all():
 
            self.Session.delete(x)
 
        self.Session.commit()
 

	
 
    def tearDown(self):
 
        for x in ChangesetComment.query().all():
 
            self.Session.delete(x)
 
        self.Session.commit()
 

	
 
        for x in Notification.query().all():
 
            self.Session.delete(x)
 
        self.Session.commit()
 

	
 
    def test_create(self):
 
        self.log_user()
 
        rev = '27cd5cce30c96924232dffcd24178a07ffeb5dfc'
 
        text = u'CommentOnRevision'
 

	
 
        params = {'text': text}
 
        response = self.app.post(url(controller='changeset', action='comment',
 
                                     repo_name=HG_REPO, revision=rev),
 
                                     params=params)
 
        # Test response...
 
        self.assertEqual(response.status, '302 Found')
 
        response.follow()
 

	
 
        response = self.app.get(url(controller='changeset', action='index',
 
                                repo_name=HG_REPO, revision=rev))
 
        # test DB
 
        self.assertEqual(ChangesetComment.query().count(), 1)
 
        self.assertTrue('''<div class="comments-number">%s '''
 
                        '''comment(s) (0 inline)</div>''' % 1 in response.body)
 

	
 
        self.assertEqual(Notification.query().count(), 1)
 
        self.assertEqual(ChangesetComment.query().count(), 1)
 

	
 
        notification = Notification.query().all()[0]
 

	
 
        ID = ChangesetComment.query().first().comment_id
 
        self.assertEqual(notification.type_,
 
                         Notification.TYPE_CHANGESET_COMMENT)
 
        sbj = (u'/vcs_test_hg/changeset/'
 
               '27cd5cce30c96924232dffcd24178a07ffeb5dfc#comment-%s' % ID)
 
        print "%s vs %s" % (sbj, notification.subject)
 
        self.assertTrue(sbj in notification.subject)
 

	
 
    def test_create_inline(self):
 
        self.log_user()
 
        rev = '27cd5cce30c96924232dffcd24178a07ffeb5dfc'
 
        text = u'CommentOnRevision'
 
        f_path = 'vcs/web/simplevcs/views/repository.py'
 
        line = 'n1'
 

	
 
        params = {'text': text, 'f_path': f_path, 'line': line}
 
        response = self.app.post(url(controller='changeset', action='comment',
 
                                     repo_name=HG_REPO, revision=rev),
 
                                     params=params)
 
        # Test response...
 
        self.assertEqual(response.status, '302 Found')
 
        response.follow()
 

	
 
        response = self.app.get(url(controller='changeset', action='index',
 
                                repo_name=HG_REPO, revision=rev))
 
        #test DB
 
        self.assertEqual(ChangesetComment.query().count(), 1)
 
        self.assertTrue('''<div class="comments-number">0 comment(s)'''
 
                        ''' (%s inline)</div>''' % 1 in response.body)
 
        self.assertTrue('''<div class="inline-comment-placeholder-line"'''
 
                        ''' line="n1" target_id="vcswebsimplevcsviews'''
 
                        '''repositorypy">''' in response.body)
 
        response.mustcontain(
 
            '''<div class="comments-number">0 comment(s)'''
 
            ''' (%s inline)</div>''' % 1
 
        )
 
        response.mustcontain(
 
            '''<div style="display:none" class="inline-comment-placeholder" '''
 
            '''path="vcs/web/simplevcs/views/repository.py" '''
 
            '''target_id="vcswebsimplevcsviewsrepositorypy">'''
 
        )
 

	
 
        self.assertEqual(Notification.query().count(), 1)
 
        self.assertEqual(ChangesetComment.query().count(), 1)
 

	
 
        notification = Notification.query().all()[0]
 
        ID = ChangesetComment.query().first().comment_id
 
        self.assertEqual(notification.type_,
 
                         Notification.TYPE_CHANGESET_COMMENT)
 
        sbj = (u'/vcs_test_hg/changeset/'
 
               '27cd5cce30c96924232dffcd24178a07ffeb5dfc#comment-%s' % ID)
 
        print "%s vs %s" % (sbj, notification.subject)
 
        self.assertTrue(sbj in notification.subject)
 

	
 
    def test_create_with_mention(self):
 
        self.log_user()
 

	
 
        rev = '27cd5cce30c96924232dffcd24178a07ffeb5dfc'
 
        text = u'@test_regular check CommentOnRevision'
 

	
 
        params = {'text':text}
 
        response = self.app.post(url(controller='changeset', action='comment',
 
                                     repo_name=HG_REPO, revision=rev),
 
                                     params=params)
 
        # Test response...
 
        self.assertEqual(response.status, '302 Found')
 
        response.follow()
 

	
 
        response = self.app.get(url(controller='changeset', action='index',
 
                                repo_name=HG_REPO, revision=rev))
 
        # test DB
 
        self.assertEqual(ChangesetComment.query().count(), 1)
 
        self.assertTrue('''<div class="comments-number">%s '''
 
                        '''comment(s) (0 inline)</div>''' % 1 in response.body)
 

	
 
        self.assertEqual(Notification.query().count(), 2)
 
        users = [x.user.username for x in UserNotification.query().all()]
 

	
 
        # test_regular get's notification by @mention
 
        self.assertEqual(sorted(users), [u'test_admin', u'test_regular'])
 

	
 
    def test_delete(self):
 
        self.log_user()
 
        rev = '27cd5cce30c96924232dffcd24178a07ffeb5dfc'
 
        text = u'CommentOnRevision'
 

	
 
        params = {'text': text}
 
        response = self.app.post(url(controller='changeset', action='comment',
 
                                     repo_name=HG_REPO, revision=rev),
 
                                     params=params)
 

	
 
        comments = ChangesetComment.query().all()
 
        self.assertEqual(len(comments), 1)
 
        comment_id = comments[0].comment_id
 

	
 
        self.app.delete(url(controller='changeset',
 
                                    action='delete_comment',
 
                                    repo_name=HG_REPO,
 
                                    comment_id=comment_id))
 

	
 
        comments = ChangesetComment.query().all()
 
        self.assertEqual(len(comments), 0)
 

	
 
        response = self.app.get(url(controller='changeset', action='index',
 
                                repo_name=HG_REPO, revision=rev))
 
        self.assertTrue('''<div class="comments-number">0 comment(s)'''
 
                        ''' (0 inline)</div>''' in response.body)
rhodecode/tests/functional/test_files.py
Show inline comments
 
@@ -36,279 +36,281 @@ class TestFilesController(TestController
 

	
 
        response.mustcontain('<a class="browser-dir ypjax-link" href="/vcs_test_hg/files/7ba66bec8d6dbba14a2155be32408c435c5f4492/docs">docs</a>')
 
        response.mustcontain('<a class="browser-dir ypjax-link" href="/vcs_test_hg/files/7ba66bec8d6dbba14a2155be32408c435c5f4492/tests">tests</a>')
 
        response.mustcontain('<a class="browser-file ypjax-link" href="/vcs_test_hg/files/7ba66bec8d6dbba14a2155be32408c435c5f4492/README.rst">README.rst</a>')
 
        response.mustcontain('1.1 KiB')
 
        response.mustcontain('text/x-python')
 

	
 
    def test_index_different_branch(self):
 
        self.log_user()
 

	
 
        response = self.app.get(url(controller='files', action='index',
 
                                    repo_name=HG_REPO,
 
                                    revision='97e8b885c04894463c51898e14387d80c30ed1ee',
 
                                    f_path='/'))
 

	
 
        response.mustcontain("""<span style="text-transform: uppercase;"><a href="#">branch: git</a></span>""")
 

	
 
    def test_index_paging(self):
 
        self.log_user()
 

	
 
        for r in [(73, 'a066b25d5df7016b45a41b7e2a78c33b57adc235'),
 
                  (92, 'cc66b61b8455b264a7a8a2d8ddc80fcfc58c221e'),
 
                  (109, '75feb4c33e81186c87eac740cee2447330288412'),
 
                  (1, '3d8f361e72ab303da48d799ff1ac40d5ac37c67e'),
 
                  (0, 'b986218ba1c9b0d6a259fac9b050b1724ed8e545')]:
 

	
 
            response = self.app.get(url(controller='files', action='index',
 
                                    repo_name=HG_REPO,
 
                                    revision=r[1],
 
                                    f_path='/'))
 

	
 
            response.mustcontain("""@ r%s:%s""" % (r[0], r[1][:12]))
 

	
 
    def test_file_source(self):
 
        self.log_user()
 
        response = self.app.get(url(controller='files', action='index',
 
                                    repo_name=HG_REPO,
 
                                    revision='27cd5cce30c96924232dffcd24178a07ffeb5dfc',
 
                                    f_path='vcs/nodes.py'))
 

	
 
        #test or history
 
        response.mustcontain("""<optgroup label="Changesets">
 
<option value="8911406ad776fdd3d0b9932a2e89677e57405a48">r167:8911406ad776 (default)</option>
 
<option value="aa957ed78c35a1541f508d2ec90e501b0a9e3167">r165:aa957ed78c35 (default)</option>
 
<option value="48e11b73e94c0db33e736eaeea692f990cb0b5f1">r140:48e11b73e94c (default)</option>
 
<option value="adf3cbf483298563b968a6c673cd5bde5f7d5eea">r126:adf3cbf48329 (default)</option>
 
<option value="6249fd0fb2cfb1411e764129f598e2cf0de79a6f">r113:6249fd0fb2cf (git)</option>
 
<option value="75feb4c33e81186c87eac740cee2447330288412">r109:75feb4c33e81 (default)</option>
 
<option value="9a4dc232ecdc763ef2e98ae2238cfcbba4f6ad8d">r108:9a4dc232ecdc (default)</option>
 
<option value="595cce4efa21fda2f2e4eeb4fe5f2a6befe6fa2d">r107:595cce4efa21 (default)</option>
 
<option value="4a8bd421fbc2dfbfb70d85a3fe064075ab2c49da">r104:4a8bd421fbc2 (default)</option>
 
<option value="57be63fc8f85e65a0106a53187f7316f8c487ffa">r102:57be63fc8f85 (default)</option>
 
<option value="5530bd87f7e2e124a64d07cb2654c997682128be">r101:5530bd87f7e2 (git)</option>
 
<option value="e516008b1c93f142263dc4b7961787cbad654ce1">r99:e516008b1c93 (default)</option>
 
<option value="41f43fc74b8b285984554532eb105ac3be5c434f">r93:41f43fc74b8b (default)</option>
 
<option value="cc66b61b8455b264a7a8a2d8ddc80fcfc58c221e">r92:cc66b61b8455 (default)</option>
 
<option value="73ab5b616b3271b0518682fb4988ce421de8099f">r91:73ab5b616b32 (default)</option>
 
<option value="e0da75f308c0f18f98e9ce6257626009fdda2b39">r82:e0da75f308c0 (default)</option>
 
<option value="fb2e41e0f0810be4d7103bc2a4c7be16ee3ec611">r81:fb2e41e0f081 (default)</option>
 
<option value="602ae2f5e7ade70b3b66a58cdd9e3e613dc8a028">r76:602ae2f5e7ad (default)</option>
 
<option value="a066b25d5df7016b45a41b7e2a78c33b57adc235">r73:a066b25d5df7 (default)</option>
 
<option value="637a933c905958ce5151f154147c25c1c7b68832">r61:637a933c9059 (web)</option>
 
<option value="0c21004effeb8ce2d2d5b4a8baf6afa8394b6fbc">r60:0c21004effeb (web)</option>
 
<option value="a1f39c56d3f1d52d5fb5920370a2a2716cd9a444">r59:a1f39c56d3f1 (web)</option>
 
<option value="97d32df05c715a3bbf936bf3cc4e32fb77fe1a7f">r58:97d32df05c71 (web)</option>
 
<option value="08eaf14517718dccea4b67755a93368341aca919">r57:08eaf1451771 (web)</option>
 
<option value="22f71ad265265a53238359c883aa976e725aa07d">r56:22f71ad26526 (web)</option>
 
<option value="97501f02b7b4330924b647755663a2d90a5e638d">r49:97501f02b7b4 (web)</option>
 
<option value="86ede6754f2b27309452bb11f997386ae01d0e5a">r47:86ede6754f2b (web)</option>
 
<option value="014c40c0203c423dc19ecf94644f7cac9d4cdce0">r45:014c40c0203c (web)</option>
 
<option value="ee87846a61c12153b51543bf860e1026c6d3dcba">r30:ee87846a61c1 (default)</option>
 
<option value="9bb326a04ae5d98d437dece54be04f830cf1edd9">r26:9bb326a04ae5 (default)</option>
 
<option value="536c1a19428381cfea92ac44985304f6a8049569">r24:536c1a194283 (default)</option>
 
<option value="dc5d2c0661b61928834a785d3e64a3f80d3aad9c">r8:dc5d2c0661b6 (default)</option>
 
<option value="3803844fdbd3b711175fc3da9bdacfcd6d29a6fb">r7:3803844fdbd3 (default)</option>
 
</optgroup>
 
<optgroup label="Branches">
 
<option selected="selected" value="27cd5cce30c96924232dffcd24178a07ffeb5dfc">default</option>
 
<option value="97e8b885c04894463c51898e14387d80c30ed1ee">git</option>
 
<option value="2e6a2bf9356ca56df08807f4ad86d480da72a8f4">web</option>
 
</optgroup>
 
<optgroup label="Tags">
 
<option selected="selected" value="27cd5cce30c96924232dffcd24178a07ffeb5dfc">tip</option>
 
<option value="fd4bdb5e9b2a29b4393a4ac6caef48c17ee1a200">0.1.4</option>
 
<option value="17544fbfcd33ffb439e2b728b5d526b1ef30bfcf">0.1.3</option>
 
<option value="a7e60bff65d57ac3a1a1ce3b12a70f8a9e8a7720">0.1.2</option>
 
<option value="eb3a60fc964309c1a318b8dfe26aa2d1586c85ae">0.1.1</option>
 
</optgroup>
 
""")
 

	
 
        response.mustcontain("""<div class="commit">merge</div>""")
 

	
 
        response.mustcontain("""<span style="text-transform: uppercase;"><a href="#">branch: default</a></span>""")
 

	
 
    def test_file_annotation(self):
 
        self.log_user()
 
        response = self.app.get(url(controller='files', action='annotate',
 
        response = self.app.get(url(controller='files', action='index',
 
                                    repo_name=HG_REPO,
 
                                    revision='27cd5cce30c96924232dffcd24178a07ffeb5dfc',
 
                                    f_path='vcs/nodes.py'))
 
                                    f_path='vcs/nodes.py',
 
                                    annotate=True))
 

	
 

	
 
        response.mustcontain("""<optgroup label="Changesets">
 
<option value="8911406ad776fdd3d0b9932a2e89677e57405a48">r167:8911406ad776 (default)</option>
 
<option value="aa957ed78c35a1541f508d2ec90e501b0a9e3167">r165:aa957ed78c35 (default)</option>
 
<option value="48e11b73e94c0db33e736eaeea692f990cb0b5f1">r140:48e11b73e94c (default)</option>
 
<option value="adf3cbf483298563b968a6c673cd5bde5f7d5eea">r126:adf3cbf48329 (default)</option>
 
<option value="6249fd0fb2cfb1411e764129f598e2cf0de79a6f">r113:6249fd0fb2cf (git)</option>
 
<option value="75feb4c33e81186c87eac740cee2447330288412">r109:75feb4c33e81 (default)</option>
 
<option value="9a4dc232ecdc763ef2e98ae2238cfcbba4f6ad8d">r108:9a4dc232ecdc (default)</option>
 
<option value="595cce4efa21fda2f2e4eeb4fe5f2a6befe6fa2d">r107:595cce4efa21 (default)</option>
 
<option value="4a8bd421fbc2dfbfb70d85a3fe064075ab2c49da">r104:4a8bd421fbc2 (default)</option>
 
<option value="57be63fc8f85e65a0106a53187f7316f8c487ffa">r102:57be63fc8f85 (default)</option>
 
<option value="5530bd87f7e2e124a64d07cb2654c997682128be">r101:5530bd87f7e2 (git)</option>
 
<option value="e516008b1c93f142263dc4b7961787cbad654ce1">r99:e516008b1c93 (default)</option>
 
<option value="41f43fc74b8b285984554532eb105ac3be5c434f">r93:41f43fc74b8b (default)</option>
 
<option value="cc66b61b8455b264a7a8a2d8ddc80fcfc58c221e">r92:cc66b61b8455 (default)</option>
 
<option value="73ab5b616b3271b0518682fb4988ce421de8099f">r91:73ab5b616b32 (default)</option>
 
<option value="e0da75f308c0f18f98e9ce6257626009fdda2b39">r82:e0da75f308c0 (default)</option>
 
<option value="fb2e41e0f0810be4d7103bc2a4c7be16ee3ec611">r81:fb2e41e0f081 (default)</option>
 
<option value="602ae2f5e7ade70b3b66a58cdd9e3e613dc8a028">r76:602ae2f5e7ad (default)</option>
 
<option value="a066b25d5df7016b45a41b7e2a78c33b57adc235">r73:a066b25d5df7 (default)</option>
 
<option value="637a933c905958ce5151f154147c25c1c7b68832">r61:637a933c9059 (web)</option>
 
<option value="0c21004effeb8ce2d2d5b4a8baf6afa8394b6fbc">r60:0c21004effeb (web)</option>
 
<option value="a1f39c56d3f1d52d5fb5920370a2a2716cd9a444">r59:a1f39c56d3f1 (web)</option>
 
<option value="97d32df05c715a3bbf936bf3cc4e32fb77fe1a7f">r58:97d32df05c71 (web)</option>
 
<option value="08eaf14517718dccea4b67755a93368341aca919">r57:08eaf1451771 (web)</option>
 
<option value="22f71ad265265a53238359c883aa976e725aa07d">r56:22f71ad26526 (web)</option>
 
<option value="97501f02b7b4330924b647755663a2d90a5e638d">r49:97501f02b7b4 (web)</option>
 
<option value="86ede6754f2b27309452bb11f997386ae01d0e5a">r47:86ede6754f2b (web)</option>
 
<option value="014c40c0203c423dc19ecf94644f7cac9d4cdce0">r45:014c40c0203c (web)</option>
 
<option value="ee87846a61c12153b51543bf860e1026c6d3dcba">r30:ee87846a61c1 (default)</option>
 
<option value="9bb326a04ae5d98d437dece54be04f830cf1edd9">r26:9bb326a04ae5 (default)</option>
 
<option value="536c1a19428381cfea92ac44985304f6a8049569">r24:536c1a194283 (default)</option>
 
<option value="dc5d2c0661b61928834a785d3e64a3f80d3aad9c">r8:dc5d2c0661b6 (default)</option>
 
<option value="3803844fdbd3b711175fc3da9bdacfcd6d29a6fb">r7:3803844fdbd3 (default)</option>
 
</optgroup>
 
<optgroup label="Branches">
 
<option selected="selected" value="27cd5cce30c96924232dffcd24178a07ffeb5dfc">default</option>
 
<option value="97e8b885c04894463c51898e14387d80c30ed1ee">git</option>
 
<option value="2e6a2bf9356ca56df08807f4ad86d480da72a8f4">web</option>
 
</optgroup>
 
<optgroup label="Tags">
 
<option selected="selected" value="27cd5cce30c96924232dffcd24178a07ffeb5dfc">tip</option>
 
<option value="fd4bdb5e9b2a29b4393a4ac6caef48c17ee1a200">0.1.4</option>
 
<option value="17544fbfcd33ffb439e2b728b5d526b1ef30bfcf">0.1.3</option>
 
<option value="a7e60bff65d57ac3a1a1ce3b12a70f8a9e8a7720">0.1.2</option>
 
<option value="eb3a60fc964309c1a318b8dfe26aa2d1586c85ae">0.1.1</option>
 
</optgroup>""")
 

	
 
        response.mustcontain("""<span style="text-transform: uppercase;"><a href="#">branch: default</a></span>""")
 

	
 
    def test_archival(self):
 
        self.log_user()
 

	
 
        for arch_ext, info in ARCHIVE_SPECS.items():
 
            fname = '27cd5cce30c96924232dffcd24178a07ffeb5dfc%s' % arch_ext
 
            filename = '%s-%s' % (HG_REPO, fname)
 

	
 
            response = self.app.get(url(controller='files', action='archivefile',
 
                                        repo_name=HG_REPO,
 
                                        fname=fname))
 

	
 
            assert response.status == '200 OK', 'wrong response code'
 
            assert response.response._headers.items() == [('Pragma', 'no-cache'),
 
            self.assertEqual(response.status, '200 OK')
 
            self.assertEqual(response.response._headers.items(),
 
             [('Pragma', 'no-cache'),
 
                                                  ('Cache-Control', 'no-cache'),
 
                                                  ('Content-Type', '%s; charset=utf-8' % info[0]),
 
                                                  ('Content-Disposition', 'attachment; filename=%s' % filename), ], 'wrong headers'
 
              ('Content-Disposition', 'attachment; filename=%s' % filename),]
 
            )
 

	
 
    def test_archival_wrong_ext(self):
 
        self.log_user()
 

	
 
        for arch_ext in ['tar', 'rar', 'x', '..ax', '.zipz']:
 
            fname = '27cd5cce30c96924232dffcd24178a07ffeb5dfc%s' % arch_ext
 

	
 
            response = self.app.get(url(controller='files', action='archivefile',
 
                                        repo_name=HG_REPO,
 
                                        fname=fname))
 
            assert 'Unknown archive type' in response.body
 

	
 
            response.mustcontain('Unknown archive type')
 

	
 
    def test_archival_wrong_revision(self):
 
        self.log_user()
 

	
 
        for rev in ['00x000000', 'tar', 'wrong', '@##$@$424213232', '232dffcd']:
 
            fname = '%s.zip' % rev
 

	
 
            response = self.app.get(url(controller='files', action='archivefile',
 
                                        repo_name=HG_REPO,
 
                                        fname=fname))
 
            assert 'Unknown revision' in response.body
 
            response.mustcontain('Unknown revision')
 

	
 
    #==========================================================================
 
    # RAW FILE
 
    #==========================================================================
 
    def test_raw_file_ok(self):
 
        self.log_user()
 
        response = self.app.get(url(controller='files', action='rawfile',
 
                                    repo_name=HG_REPO,
 
                                    revision='27cd5cce30c96924232dffcd24178a07ffeb5dfc',
 
                                    f_path='vcs/nodes.py'))
 

	
 
        assert response.content_disposition == "attachment; filename=nodes.py"
 
        assert response.content_type == "text/x-python"
 
        self.assertEqual(response.content_disposition, "attachment; filename=nodes.py")
 
        self.assertEqual(response.content_type, "text/x-python")
 

	
 
    def test_raw_file_wrong_cs(self):
 
        self.log_user()
 
        rev = u'ERRORce30c96924232dffcd24178a07ffeb5dfc'
 
        f_path = 'vcs/nodes.py'
 

	
 
        response = self.app.get(url(controller='files', action='rawfile',
 
                                    repo_name=HG_REPO,
 
                                    revision=rev,
 
                                    f_path=f_path))
 

	
 
        msg = """Revision %r does not exist for this repository""" % (rev)
 
        self.checkSessionFlash(response, msg)
 

	
 
        msg = """%s""" % (HG_REPO)
 
        self.checkSessionFlash(response, msg)
 

	
 
    def test_raw_file_wrong_f_path(self):
 
        self.log_user()
 
        rev = '27cd5cce30c96924232dffcd24178a07ffeb5dfc'
 
        f_path = 'vcs/ERRORnodes.py'
 
        response = self.app.get(url(controller='files', action='rawfile',
 
                                    repo_name=HG_REPO,
 
                                    revision=rev,
 
                                    f_path=f_path))
 

	
 
        msg = "There is no file nor directory at the given path: %r at revision %r" % (f_path, rev[:12])
 
        self.checkSessionFlash(response, msg)
 

	
 
    #==========================================================================
 
    # RAW RESPONSE - PLAIN
 
    #==========================================================================
 
    def test_raw_ok(self):
 
        self.log_user()
 
        response = self.app.get(url(controller='files', action='raw',
 
                                    repo_name=HG_REPO,
 
                                    revision='27cd5cce30c96924232dffcd24178a07ffeb5dfc',
 
                                    f_path='vcs/nodes.py'))
 

	
 
        assert response.content_type == "text/plain"
 
        self.assertEqual(response.content_type, "text/plain")
 

	
 
    def test_raw_wrong_cs(self):
 
        self.log_user()
 
        rev = u'ERRORcce30c96924232dffcd24178a07ffeb5dfc'
 
        f_path = 'vcs/nodes.py'
 

	
 
        response = self.app.get(url(controller='files', action='raw',
 
                                    repo_name=HG_REPO,
 
                                    revision=rev,
 
                                    f_path=f_path))
 
        msg = """Revision %r does not exist for this repository""" % (rev)
 
        self.checkSessionFlash(response, msg)
 

	
 
        msg = """%s""" % (HG_REPO)
 
        self.checkSessionFlash(response, msg)
 

	
 
    def test_raw_wrong_f_path(self):
 
        self.log_user()
 
        rev = '27cd5cce30c96924232dffcd24178a07ffeb5dfc'
 
        f_path = 'vcs/ERRORnodes.py'
 
        response = self.app.get(url(controller='files', action='raw',
 
                                    repo_name=HG_REPO,
 
                                    revision=rev,
 
                                    f_path=f_path))
 
        msg = "There is no file nor directory at the given path: %r at revision %r" % (f_path, rev[:12])
 
        self.checkSessionFlash(response, msg)
 

	
 
    def test_ajaxed_files_list(self):
 
        self.log_user()
 
        rev = '27cd5cce30c96924232dffcd24178a07ffeb5dfc'
 
        response = self.app.get(
 
            url('files_nodelist_home', repo_name=HG_REPO,f_path='/',revision=rev),
 
            extra_environ={'HTTP_X_PARTIAL_XHR': '1'},
 
        )
 
        response.mustcontain("vcs/web/simplevcs/views/repository.py")
rhodecode/tests/functional/test_forks.py
Show inline comments
 
from rhodecode.tests import *
 

	
 
from rhodecode.model.db import Repository
 
from rhodecode.model.repo import RepoModel
 
from rhodecode.model.user import UserModel
 

	
 

	
 
class TestForksController(TestController):
 

	
 
    def setUp(self):
 
        self.username = u'forkuser'
 
        self.password = u'qweqwe'
 
        self.u1 = UserModel().create_or_update(
 
            username=self.username, password=self.password,
 
            email=u'fork_king@rhodecode.org', name=u'u1', lastname=u'u1'
 
        )
 
        self.Session.commit()
 

	
 
    def tearDown(self):
 
        self.Session.delete(self.u1)
 
        self.Session.commit()
 

	
 
    def test_index(self):
 
        self.log_user()
 
        repo_name = HG_REPO
 
        response = self.app.get(url(controller='forks', action='forks',
 
                                    repo_name=repo_name))
 

	
 
        self.assertTrue("""There are no forks yet""" in response.body)
 

	
 

	
 
    def test_index_with_fork(self):
 
        self.log_user()
 

	
 
        # create a fork
 
        fork_name = HG_FORK
 
        description = 'fork of vcs test'
 
        repo_name = HG_REPO
 
        org_repo = Repository.get_by_repo_name(repo_name)
 
        response = self.app.post(url(controller='forks',
 
                                     action='fork_create',
 
                                    repo_name=repo_name),
 
                                    {'repo_name':fork_name,
 
                                     'repo_group':'',
 
                                     'fork_parent_id':org_repo.repo_id,
 
                                     'repo_type':'hg',
 
                                     'description':description,
 
                                     'private':'False'})
 

	
 
        response = self.app.get(url(controller='forks', action='forks',
 
                                    repo_name=repo_name))
 

	
 

	
 
        self.assertTrue("""<a href="/%s/summary">"""
 
                         """vcs_test_hg_fork</a>""" % fork_name
 
                         in response.body)
 

	
 
        #remove this fork
 
        response = self.app.delete(url('repo', repo_name=fork_name))
 

	
 

	
 

	
 

	
 
    def test_z_fork_create(self):
 
        self.log_user()
 
        fork_name = HG_FORK
 
        description = 'fork of vcs test'
 
        repo_name = HG_REPO
 
        org_repo = Repository.get_by_repo_name(repo_name)
 
        response = self.app.post(url(controller='forks', action='fork_create',
 
                                    repo_name=repo_name),
 
                                    {'repo_name':fork_name,
 
                                     'repo_group':'',
 
                                     'fork_parent_id':org_repo.repo_id,
 
                                     'repo_type':'hg',
 
                                     'description':description,
 
                                     'private':'False'})
 

	
 
        #test if we have a message that fork is ok
 
        self.assertTrue('forked %s repository as %s' \
 
                      % (repo_name, fork_name) in response.session['flash'][0])
 

	
 
        #test if the fork was created in the database
 
        fork_repo = self.Session.query(Repository)\
 
            .filter(Repository.repo_name == fork_name).one()
 

	
 
        self.assertEqual(fork_repo.repo_name, fork_name)
 
        self.assertEqual(fork_repo.fork.repo_name, repo_name)
 

	
 

	
 
        #test if fork is visible in the list ?
 
        response = response.follow()
 

	
 

	
 
        # check if fork is marked as fork
 
        # wait for cache to expire
 
        import time
 
        time.sleep(10)
 
        response = self.app.get(url(controller='summary', action='index',
 
                                    repo_name=fork_name))
 

	
 
        self.assertTrue('Fork of %s' % repo_name in response.body)
 

	
 
    def test_zz_fork_permission_page(self):
 
        usr = self.log_user(self.username, self.password)['user_id']
 
        repo_name = HG_REPO
 

	
 
        forks = self.Session.query(Repository)\
 
            .filter(Repository.fork_id != None)\
 
            .all()
 
        self.assertEqual(1, len(forks))
 

	
 
        # set read permissions for this
 
        RepoModel().grant_user_permission(repo=forks[0],
 
                                          user=usr,
 
                                          perm='repository.read')
 
        self.Session.commit()
 

	
 
        response = self.app.get(url(controller='forks', action='forks',
 
                                    repo_name=repo_name))
 

	
 
        response.mustcontain('<div style="padding:5px 3px 3px 42px;">fork of vcs test</div>')
 

	
 
    def test_zzz_fork_permission_page(self):
 
        usr = self.log_user(self.username, self.password)['user_id']
 
        repo_name = HG_REPO
 

	
 
        forks = self.Session.query(Repository)\
 
            .filter(Repository.fork_id != None)\
 
            .all()
 
        self.assertEqual(1, len(forks))
 

	
 
        # set none
 
        RepoModel().grant_user_permission(repo=forks[0],
 
                                          user=usr, perm='repository.none')
 
        self.Session.commit()
 
        # fork shouldn't be there
 
        response = self.app.get(url(controller='forks', action='forks',
 
                                    repo_name=repo_name))
 
        response.mustcontain('There are no forks yet')
rhodecode/tests/functional/test_login.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
from rhodecode.tests import *
 
from rhodecode.model.db import User, Notification
 
from rhodecode.lib.utils2 import generate_api_key
 
from rhodecode.lib.auth import check_password
 
from rhodecode.model.meta import Session
 

	
 

	
 
class TestLoginController(TestController):
 

	
 
    def tearDown(self):
 
        for n in Notification.query().all():
 
            Session.delete(n)
 

	
 
        Session.commit()
 
        self.assertEqual(Notification.query().all(), [])
 

	
 
    def test_index(self):
 
        response = self.app.get(url(controller='login', action='index'))
 
        self.assertEqual(response.status, '200 OK')
 
        # Test response...
 

	
 
    def test_login_admin_ok(self):
 
        response = self.app.post(url(controller='login', action='index'),
 
                                 {'username':'test_admin',
 
                                  'password':'test12'})
 
        self.assertEqual(response.status, '302 Found')
 
        self.assertEqual(response.session['rhodecode_user'].get('username') ,
 
                         'test_admin')
 
        response = response.follow()
 
        self.assertTrue('%s repository' % HG_REPO in response.body)
 

	
 
    def test_login_regular_ok(self):
 
        response = self.app.post(url(controller='login', action='index'),
 
                                 {'username':'test_regular',
 
                                  'password':'test12'})
 

	
 
        self.assertEqual(response.status, '302 Found')
 
        self.assertEqual(response.session['rhodecode_user'].get('username') ,
 
                         'test_regular')
 
        response = response.follow()
 
        self.assertTrue('%s repository' % HG_REPO in response.body)
 
        self.assertTrue('<a title="Admin" href="/_admin">' not in response.body)
 

	
 
    def test_login_ok_came_from(self):
 
        test_came_from = '/_admin/users'
 
        response = self.app.post(url(controller='login', action='index',
 
                                     came_from=test_came_from),
 
                                 {'username':'test_admin',
 
                                  'password':'test12'})
 
        self.assertEqual(response.status, '302 Found')
 
        response = response.follow()
 

	
 
        self.assertEqual(response.status, '200 OK')
 
        self.assertTrue('Users administration' in response.body)
 

	
 

	
 
    def test_login_short_password(self):
 
        response = self.app.post(url(controller='login', action='index'),
 
                                 {'username':'test_admin',
 
                                  'password':'as'})
 
        self.assertEqual(response.status, '200 OK')
 

	
 
        self.assertTrue('Enter 3 characters or more' in response.body)
 

	
 
    def test_login_wrong_username_password(self):
 
        response = self.app.post(url(controller='login', action='index'),
 
                                 {'username':'error',
 
                                  'password':'test12'})
 
        self.assertEqual(response.status , '200 OK')
 

	
 
        self.assertTrue('invalid user name' in response.body)
 
        self.assertTrue('invalid password' in response.body)
 

	
 
    #==========================================================================
 
    # REGISTRATIONS
 
    #==========================================================================
 
    def test_register(self):
 
        response = self.app.get(url(controller='login', action='register'))
 
        self.assertTrue('Sign Up to RhodeCode' in response.body)
 

	
 
    def test_register_err_same_username(self):
 
        response = self.app.post(url(controller='login', action='register'),
 
                                            {'username':'test_admin',
 
                                             'password':'test12',
 
                                             'password_confirmation':'test12',
 
                                             'email':'goodmail@domain.com',
 
                                             'name':'test',
 
                                             'lastname':'test'})
 

	
 
        self.assertEqual(response.status , '200 OK')
 
        self.assertTrue('This username already exists' in response.body)
 

	
 
    def test_register_err_same_email(self):
 
        response = self.app.post(url(controller='login', action='register'),
 
                                            {'username':'test_admin_0',
 
                                             'password':'test12',
 
                                             'password_confirmation':'test12',
 
                                             'email':'test_admin@mail.com',
 
                                             'name':'test',
 
                                             'lastname':'test'})
 

	
 
        self.assertEqual(response.status , '200 OK')
 
        assert 'This e-mail address is already taken' in response.body
 
        response.mustcontain('This e-mail address is already taken')
 

	
 
    def test_register_err_same_email_case_sensitive(self):
 
        response = self.app.post(url(controller='login', action='register'),
 
                                            {'username':'test_admin_1',
 
                                             'password':'test12',
 
                                             'password_confirmation':'test12',
 
                                             'email':'TesT_Admin@mail.COM',
 
                                             'name':'test',
 
                                             'lastname':'test'})
 
        self.assertEqual(response.status , '200 OK')
 
        assert 'This e-mail address is already taken' in response.body
 
        response.mustcontain('This e-mail address is already taken')
 

	
 
    def test_register_err_wrong_data(self):
 
        response = self.app.post(url(controller='login', action='register'),
 
                                            {'username':'xs',
 
                                             'password':'test',
 
                                             'password_confirmation':'test',
 
                                             'email':'goodmailm',
 
                                             'name':'test',
 
                                             'lastname':'test'})
 
        self.assertEqual(response.status , '200 OK')
 
        assert 'An email address must contain a single @' in response.body
 
        assert 'Enter a value 6 characters long or more' in response.body
 

	
 
        response.mustcontain('An email address must contain a single @')
 
        response.mustcontain('Enter a value 6 characters long or more')
 

	
 
    def test_register_err_username(self):
 
        response = self.app.post(url(controller='login', action='register'),
 
                                            {'username':'error user',
 
                                             'password':'test12',
 
                                             'password_confirmation':'test12',
 
                                             'email':'goodmailm',
 
                                             'name':'test',
 
                                             'lastname':'test'})
 

	
 
        self.assertEqual(response.status , '200 OK')
 
        assert 'An email address must contain a single @' in response.body
 
        assert ('Username may only contain '
 
        response.mustcontain('An email address must contain a single @')
 
        response.mustcontain('Username may only contain '
 
                'alphanumeric characters underscores, '
 
                'periods or dashes and must begin with '
 
                'alphanumeric character') in response.body
 
                'alphanumeric character')
 

	
 
    def test_register_err_case_sensitive(self):
 
        response = self.app.post(url(controller='login', action='register'),
 
                                            {'username':'Test_Admin',
 
                                             'password':'test12',
 
                                             'password_confirmation':'test12',
 
                                             'email':'goodmailm',
 
                                             'name':'test',
 
                                             'lastname':'test'})
 

	
 
        self.assertEqual(response.status , '200 OK')
 
        self.assertTrue('An email address must contain a single @' in response.body)
 
        self.assertTrue('This username already exists' in response.body)
 

	
 

	
 

	
 
    def test_register_special_chars(self):
 
        response = self.app.post(url(controller='login', action='register'),
 
                                            {'username':'xxxaxn',
 
                                             'password':'ąćźżąśśśś',
 
                                             'password_confirmation':'ąćźżąśśśś',
 
                                             'email':'goodmailm@test.plx',
 
                                             'name':'test',
 
                                             'lastname':'test'})
 

	
 
        self.assertEqual(response.status , '200 OK')
 
        self.assertTrue('Invalid characters in password' in response.body)
 

	
 

	
 
    def test_register_password_mismatch(self):
 
        response = self.app.post(url(controller='login', action='register'),
 
                                            {'username':'xs',
 
                                             'password':'123qwe',
 
                                             'password_confirmation':'qwe123',
 
                                             'email':'goodmailm@test.plxa',
 
                                             'name':'test',
 
                                             'lastname':'test'})
 

	
 
        self.assertEqual(response.status , '200 OK')
 
        assert 'Passwords do not match' in response.body
 
        response.mustcontain('Passwords do not match')
 

	
 
    def test_register_ok(self):
 
        username = 'test_regular4'
 
        password = 'qweqwe'
 
        email = 'marcin@test.com'
 
        name = 'testname'
 
        lastname = 'testlastname'
 

	
 
        response = self.app.post(url(controller='login', action='register'),
 
                                            {'username':username,
 
                                             'password':password,
 
                                             'password_confirmation':password,
 
                                             'email':email,
 
                                             'name':name,
 
                                             'lastname':lastname})
 
                                             'lastname':lastname,
 
                                             'admin':True}) # This should be overriden
 
        self.assertEqual(response.status , '302 Found')
 
        assert 'You have successfully registered into rhodecode' in response.session['flash'][0], 'No flash message about user registration'
 
        self.checkSessionFlash(response, 'You have successfully registered into rhodecode')
 

	
 
        ret = self.Session.query(User).filter(User.username == 'test_regular4').one()
 
        assert ret.username == username , 'field mismatch %s %s' % (ret.username, username)
 
        assert check_password(password, ret.password) == True , 'password mismatch'
 
        assert ret.email == email , 'field mismatch %s %s' % (ret.email, email)
 
        assert ret.name == name , 'field mismatch %s %s' % (ret.name, name)
 
        assert ret.lastname == lastname , 'field mismatch %s %s' % (ret.lastname, lastname)
 

	
 
        self.assertEqual(ret.username, username)
 
        self.assertEqual(check_password(password, ret.password), True)
 
        self.assertEqual(ret.email, email)
 
        self.assertEqual(ret.name, name)
 
        self.assertEqual(ret.lastname, lastname)
 
        self.assertNotEqual(ret.api_key, None)
 
        self.assertEqual(ret.admin, False)
 

	
 
    def test_forgot_password_wrong_mail(self):
 
        response = self.app.post(url(controller='login', action='password_reset'),
 
                                            {'email':'marcin@wrongmail.org', })
 
        response = self.app.post(
 
                        url(controller='login', action='password_reset'),
 
                            {'email': 'marcin@wrongmail.org',}
 
        )
 

	
 
        assert "This e-mail address doesn't exist" in response.body, 'Missing error message about wrong email'
 
        response.mustcontain("This e-mail address doesn't exist")
 

	
 
    def test_forgot_password(self):
 
        response = self.app.get(url(controller='login',
 
                                    action='password_reset'))
 
        self.assertEqual(response.status , '200 OK')
 

	
 
        username = 'test_password_reset_1'
 
        password = 'qweqwe'
 
        email = 'marcin@python-works.com'
 
        name = 'passwd'
 
        lastname = 'reset'
 

	
 
        new = User()
 
        new.username = username
 
        new.password = password
 
        new.email = email
 
        new.name = name
 
        new.lastname = lastname
 
        new.api_key = generate_api_key(username)
 
        self.Session.add(new)
 
        self.Session.commit()
 

	
 
        response = self.app.post(url(controller='login',
 
                                     action='password_reset'),
 
                                 {'email':email, })
 

	
 
        self.checkSessionFlash(response, 'Your password reset link was sent')
 

	
 
        response = response.follow()
 

	
 
        # BAD KEY
 

	
 
        key = "bad"
 
        response = self.app.get(url(controller='login',
 
                                    action='password_reset_confirmation',
 
                                    key=key))
 
        self.assertEqual(response.status, '302 Found')
 
        self.assertTrue(response.location.endswith(url('reset_password')))
 

	
 
        # GOOD KEY
 

	
 
        key = User.get_by_username(username).api_key
 
        response = self.app.get(url(controller='login',
 
                                    action='password_reset_confirmation',
 
                                    key=key))
 
        self.assertEqual(response.status, '302 Found')
 
        self.assertTrue(response.location.endswith(url('login_home')))
 

	
 
        self.checkSessionFlash(response,
 
                               ('Your password reset was successful, '
 
                                'new password has been sent to your email'))
 

	
 
        response = response.follow()
rhodecode/tests/mem_watch
Show inline comments
 
ps -eo size,pid,user,command --sort -size | awk '{ hr=$1/1024 ; printf("%13.2f Mb ",hr) } { for ( x=4 ; x<=NF ; x++ ) { printf("%s ",$x) } print "" }'|grep paster
 
ps -eo size,pid,user,command --sort -size | awk '{ hr=$1/1024 ; printf("%13.2f Mb ",hr) } { for ( x=4 ; x<=NF ; x++ ) { printf("%s ",$x) } print "" }'|grep [p]aster
rhodecode/tests/rhodecode_crawler.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
"""
 
    rhodecode.tests.test_crawer
 
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~
 

	
 
    Test for crawling a project for memory usage
 
    This should be runned just as regular script together
 
    with a watch script that will show memory usage.
 

	
 
    watch -n1 ./rhodecode/tests/mem_watch
 

	
 
    :created_on: Apr 21, 2010
 
    :author: marcink
 
    :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
 
    :license: GPLv3, see COPYING for more details.
 
"""
 
# This program is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# This program is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 

	
 

	
 
import cookielib
 
import urllib
 
import urllib2
 
import time
 

	
 
import os
 
import sys
 
from os.path import join as jn
 
from os.path import dirname as dn
 

	
 
__here__ = os.path.abspath(__file__)
 
__root__ = dn(dn(dn(__here__)))
 
sys.path.append(__root__)
 

	
 
from rhodecode.lib import vcs
 
from rhodecode.lib.compat import OrderedSet
 
from rhodecode.lib.vcs.exceptions import RepositoryError
 

	
 
BASE_URI = 'http://127.0.0.1:5000/%s'
 
PROJECT = 'CPython'
 
PASES = 3
 
HOST = 'http://127.0.0.1'
 
PORT = 5000
 
BASE_URI = '%s:%s/' % (HOST, PORT)
 

	
 
if len(sys.argv) == 2:
 
    BASE_URI = sys.argv[1]
 

	
 
if not BASE_URI.endswith('/'):
 
    BASE_URI += '/'
 

	
 
print 'Crawling @ %s' % BASE_URI
 
BASE_URI += '%s'
 
PROJECT_PATH = jn('/', 'home', 'marcink', 'hg_repos')
 
PROJECTS = [
 
    'linux-magx-pbranch',
 
    'CPython',
 
    'rhodecode_tip',
 
]
 

	
 

	
 
cj = cookielib.FileCookieJar('/tmp/rc_test_cookie.txt')
 
o = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))
 
o.addheaders = [
 
                     ('User-agent', 'rhodecode-crawler'),
 
                     ('Accept-Language', 'en - us, en;q = 0.5')
 
                    ]
 

	
 
urllib2.install_opener(o)
 

	
 

	
 
def test_changelog_walk(pages=100):
 
def _get_repo(proj):
 
    if isinstance(proj, basestring):
 
        repo = vcs.get_repo(jn(PROJECT_PATH, proj))
 
        proj = proj
 
    else:
 
        repo = proj
 
        proj = repo.name
 

	
 
    return repo, proj
 

	
 

	
 
def test_changelog_walk(proj, pages=100):
 
    repo, proj = _get_repo(proj)
 

	
 
    total_time = 0
 
    for i in range(1, pages):
 

	
 
        page = '/'.join((PROJECT, 'changelog',))
 
        page = '/'.join((proj, 'changelog',))
 

	
 
        full_uri = (BASE_URI % page) + '?' + urllib.urlencode({'page':i})
 
        s = time.time()
 
        f = o.open(full_uri)
 
        size = len(f.read())
 
        e = time.time() - s
 
        total_time += e
 
        print 'visited %s size:%s req:%s ms' % (full_uri, size, e)
 

	
 
    print 'total_time', total_time
 
    print 'average on req', total_time / float(pages)
 

	
 

	
 
def test_changeset_walk(limit=None):
 
    print 'processing', jn(PROJECT_PATH, PROJECT)
 
def test_changeset_walk(proj, limit=None):
 
    repo, proj = _get_repo(proj)
 

	
 
    print 'processing', jn(PROJECT_PATH, proj)
 
    total_time = 0
 

	
 
    repo = vcs.get_repo(jn(PROJECT_PATH, PROJECT))
 
    cnt = 0
 
    for i in repo:
 
        cnt += 1
 
        raw_cs = '/'.join((PROJECT, 'changeset', i.raw_id))
 
        raw_cs = '/'.join((proj, 'changeset', i.raw_id))
 
        if limit and limit == cnt:
 
            break
 

	
 
        full_uri = (BASE_URI % raw_cs)
 
        print '%s visiting %s\%s' % (cnt, full_uri, i)
 
        s = time.time()
 
        f = o.open(full_uri)
 
        size = len(f.read())
 
        e = time.time() - s
 
        total_time += e
 
        print '%s visited %s\%s size:%s req:%s ms' % (cnt, full_uri, i, size, e)
 

	
 
    print 'total_time', total_time
 
    print 'average on req', total_time / float(cnt)
 

	
 

	
 
def test_files_walk(limit=100):
 
    print 'processing', jn(PROJECT_PATH, PROJECT)
 
    total_time = 0
 
def test_files_walk(proj, limit=100):
 
    repo, proj = _get_repo(proj)
 

	
 
    repo = vcs.get_repo(jn(PROJECT_PATH, PROJECT))
 

	
 
    from rhodecode.lib.compat import OrderedSet
 
    from rhodecode.lib.vcs.exceptions import RepositoryError
 
    print 'processing', jn(PROJECT_PATH, proj)
 
    total_time = 0
 

	
 
    paths_ = OrderedSet([''])
 
    try:
 
        tip = repo.get_changeset('tip')
 
        for topnode, dirs, files in tip.walk('/'):
 

	
 
            for dir in dirs:
 
                paths_.add(dir.path)
 
                for f in dir:
 
                    paths_.add(f.path)
 

	
 
            for f in files:
 
                paths_.add(f.path)
 

	
 
    except RepositoryError, e:
 
        pass
 

	
 
    cnt = 0
 
    for f in paths_:
 
        cnt += 1
 
        if limit and limit == cnt:
 
            break
 

	
 
        file_path = '/'.join((PROJECT, 'files', 'tip', f))
 

	
 
        file_path = '/'.join((proj, 'files', 'tip', f))
 
        full_uri = (BASE_URI % file_path)
 
        print '%s visiting %s' % (cnt, full_uri)
 
        s = time.time()
 
        f = o.open(full_uri)
 
        size = len(f.read())
 
        e = time.time() - s
 
        total_time += e
 
        print '%s visited %s size:%s req:%s ms' % (cnt, full_uri, size, e)
 
        print '%s visited OK size:%s req:%s ms' % (cnt, size, e)
 

	
 
    print 'total_time', total_time
 
    print 'average on req', total_time / float(cnt)
 

	
 

	
 
test_changelog_walk(40)
 
time.sleep(2)
 
test_changeset_walk(limit=100)
 
time.sleep(2)
 
test_files_walk(100)
 
if __name__ == '__main__':
 
    for path in PROJECTS:
 
        repo = vcs.get_repo(jn(PROJECT_PATH, path))
 
        for i in range(PASES):
 
            print 'PASS %s/%s' % (i, PASES)
 
            test_changelog_walk(repo, pages=80)
 
            test_changeset_walk(repo, limit=100)
 
            test_files_walk(repo, limit=100)
rhodecode/tests/test_libs.py
Show inline comments
 
@@ -10,102 +10,109 @@
 
    :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
 
    :license: GPLv3, see COPYING for more details.
 
"""
 
# This program is free software: you can redistribute it and/or modify
 
# it under the terms of the GNU General Public License as published by
 
# the Free Software Foundation, either version 3 of the License, or
 
# (at your option) any later version.
 
#
 
# This program is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 

	
 

	
 

	
 
import unittest
 
from rhodecode.tests import *
 

	
 
proto = 'http'
 
TEST_URLS = [
 
    ('%s://127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
 
     '%s://127.0.0.1' % proto),
 
    ('%s://marcink@127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
 
     '%s://127.0.0.1' % proto),
 
    ('%s://marcink:pass@127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
 
     '%s://127.0.0.1' % proto),
 
    ('%s://127.0.0.1:8080' % proto, ['%s://' % proto, '127.0.0.1', '8080'],
 
     '%s://127.0.0.1:8080' % proto),
 
    ('%s://domain.org' % proto, ['%s://' % proto, 'domain.org'],
 
     '%s://domain.org' % proto),
 
    ('%s://user:pass@domain.org:8080' % proto, ['%s://' % proto, 'domain.org',
 
                                                '8080'],
 
     '%s://domain.org:8080' % proto),
 
]
 

	
 
proto = 'https'
 
TEST_URLS += [
 
    ('%s://127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
 
     '%s://127.0.0.1' % proto),
 
    ('%s://marcink@127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
 
     '%s://127.0.0.1' % proto),
 
    ('%s://marcink:pass@127.0.0.1' % proto, ['%s://' % proto, '127.0.0.1'],
 
     '%s://127.0.0.1' % proto),
 
    ('%s://127.0.0.1:8080' % proto, ['%s://' % proto, '127.0.0.1', '8080'],
 
     '%s://127.0.0.1:8080' % proto),
 
    ('%s://domain.org' % proto, ['%s://' % proto, 'domain.org'],
 
     '%s://domain.org' % proto),
 
    ('%s://user:pass@domain.org:8080' % proto, ['%s://' % proto, 'domain.org',
 
                                                '8080'],
 
     '%s://domain.org:8080' % proto),
 
]
 

	
 

	
 
class TestLibs(unittest.TestCase):
 

	
 
    def test_uri_filter(self):
 
        from rhodecode.lib.utils2 import uri_filter
 

	
 
        for url in TEST_URLS:
 
            self.assertEqual(uri_filter(url[0]), url[1])
 

	
 
    def test_credentials_filter(self):
 
        from rhodecode.lib.utils2 import credentials_filter
 

	
 
        for url in TEST_URLS:
 
            self.assertEqual(credentials_filter(url[0]), url[2])
 

	
 
    def test_str2bool(self):
 
        from rhodecode.lib.utils2 import str2bool
 
        test_cases = [
 
            ('t', True),
 
            ('true', True),
 
            ('y', True),
 
            ('yes', True),
 
            ('on', True),
 
            ('1', True),
 
            ('Y', True),
 
            ('yeS', True),
 
            ('Y', True),
 
            ('TRUE', True),
 
            ('T', True),
 
            ('False', False),
 
            ('F', False),
 
            ('FALSE', False),
 
            ('0', False),
 
            ('-1', False),
 
            ('', False), ]
 

	
 
        for case in test_cases:
 
            self.assertEqual(str2bool(case[0]), case[1])
 

	
 
    def test_mention_extractor(self):
 
        from rhodecode.lib.utils2 import extract_mentioned_users
 
        sample = ("@first hi there @marcink here's my email marcin@email.com "
 
                  "@lukaszb check it pls @ ttwelve @D[] @one@two@three "
 
                  "@MARCIN    @maRCiN @2one_more22")
 
        s = ['2one_more22', 'D', 'MARCIN', 'first', 'lukaszb',
 
             'maRCiN', 'marcink', 'one']
 
        sample = (
 
            "@first hi there @marcink here's my email marcin@email.com "
 
            "@lukaszb check @one_more22 it pls @ ttwelve @D[] @one@two@three "
 
            "@MARCIN    @maRCiN @2one_more22 @john please see this http://org.pl "
 
            "@marian.user just do it @marco-polo and next extract @marco_polo "
 
            "user.dot  hej ! not-needed maril@domain.org"
 
        )
 

	
 
        s = sorted([
 
        'first', 'marcink', 'lukaszb', 'one_more22', 'MARCIN', 'maRCiN', 'john',
 
        'marian.user', 'marco-polo', 'marco_polo'
 
        ], key=lambda k: k.lower())
 
        self.assertEqual(s, extract_mentioned_users(sample))
rhodecode/tests/test_models.py
Show inline comments
 
import os
 
import unittest
 
from rhodecode.tests import *
 

	
 
from rhodecode.model.repos_group import ReposGroupModel
 
from rhodecode.model.repo import RepoModel
 
from rhodecode.model.db import RepoGroup, User, Notification, UserNotification, \
 
    UsersGroup, UsersGroupMember, Permission, UsersGroupRepoGroupToPerm
 
    UsersGroup, UsersGroupMember, Permission, UsersGroupRepoGroupToPerm,\
 
    Repository
 
from sqlalchemy.exc import IntegrityError
 
from rhodecode.model.user import UserModel
 

	
 
from rhodecode.model.meta import Session
 
from rhodecode.model.notification import NotificationModel
 
from rhodecode.model.users_group import UsersGroupModel
 
from rhodecode.lib.auth import AuthUser
 

	
 

	
 
def _make_group(path, desc='desc', parent_id=None,
 
                 skip_if_exists=False):
 

	
 
    gr = RepoGroup.get_by_group_name(path)
 
    if gr and skip_if_exists:
 
        return gr
 

	
 
    gr = ReposGroupModel().create(path, desc, parent_id)
 
    return gr
 

	
 

	
 
class TestReposGroups(unittest.TestCase):
 

	
 
    def setUp(self):
 
        self.g1 = _make_group('test1', skip_if_exists=True)
 
        Session.commit()
 
        self.g2 = _make_group('test2', skip_if_exists=True)
 
        Session.commit()
 
        self.g3 = _make_group('test3', skip_if_exists=True)
 
        Session.commit()
 

	
 
    def tearDown(self):
 
        print 'out'
 

	
 
    def __check_path(self, *path):
 
        """
 
        Checks the path for existance !
 
        """
 
        path = [TESTS_TMP_PATH] + list(path)
 
        path = os.path.join(*path)
 
        return os.path.isdir(path)
 

	
 
    def _check_folders(self):
 
        print os.listdir(TESTS_TMP_PATH)
 

	
 
    def __delete_group(self, id_):
 
        ReposGroupModel().delete(id_)
 

	
 
    def __update_group(self, id_, path, desc='desc', parent_id=None):
 
        form_data = dict(
 
            group_name=path,
 
            group_description=desc,
 
            group_parent_id=parent_id,
 
            perms_updates=[],
 
            perms_new=[]
 
        )
 
        gr = ReposGroupModel().update(id_, form_data)
 
        return gr
 

	
 
    def test_create_group(self):
 
        g = _make_group('newGroup')
 
        self.assertEqual(g.full_path, 'newGroup')
 

	
 
        self.assertTrue(self.__check_path('newGroup'))
 

	
 
    def test_create_same_name_group(self):
 
        self.assertRaises(IntegrityError, lambda:_make_group('newGroup'))
 
        Session.rollback()
 

	
 
    def test_same_subgroup(self):
 
        sg1 = _make_group('sub1', parent_id=self.g1.group_id)
 
        self.assertEqual(sg1.parent_group, self.g1)
 
        self.assertEqual(sg1.full_path, 'test1/sub1')
 
        self.assertTrue(self.__check_path('test1', 'sub1'))
 

	
 
        ssg1 = _make_group('subsub1', parent_id=sg1.group_id)
 
        self.assertEqual(ssg1.parent_group, sg1)
 
        self.assertEqual(ssg1.full_path, 'test1/sub1/subsub1')
 
        self.assertTrue(self.__check_path('test1', 'sub1', 'subsub1'))
 

	
 
    def test_remove_group(self):
 
        sg1 = _make_group('deleteme')
 
        self.__delete_group(sg1.group_id)
 

	
 
        self.assertEqual(RepoGroup.get(sg1.group_id), None)
 
        self.assertFalse(self.__check_path('deteteme'))
 

	
 
        sg1 = _make_group('deleteme', parent_id=self.g1.group_id)
 
        self.__delete_group(sg1.group_id)
 

	
 
        self.assertEqual(RepoGroup.get(sg1.group_id), None)
 
        self.assertFalse(self.__check_path('test1', 'deteteme'))
 

	
 
    def test_rename_single_group(self):
 
        sg1 = _make_group('initial')
 

	
 
        new_sg1 = self.__update_group(sg1.group_id, 'after')
 
        self.assertTrue(self.__check_path('after'))
 
        self.assertEqual(RepoGroup.get_by_group_name('initial'), None)
 

	
 
    def test_update_group_parent(self):
 

	
 
        sg1 = _make_group('initial', parent_id=self.g1.group_id)
 

	
 
        new_sg1 = self.__update_group(sg1.group_id, 'after', parent_id=self.g1.group_id)
 
        self.assertTrue(self.__check_path('test1', 'after'))
 
        self.assertEqual(RepoGroup.get_by_group_name('test1/initial'), None)
 

	
 
        new_sg1 = self.__update_group(sg1.group_id, 'after', parent_id=self.g3.group_id)
 
        self.assertTrue(self.__check_path('test3', 'after'))
 
        self.assertEqual(RepoGroup.get_by_group_name('test3/initial'), None)
 

	
 
        new_sg1 = self.__update_group(sg1.group_id, 'hello')
 
        self.assertTrue(self.__check_path('hello'))
 

	
 
        self.assertEqual(RepoGroup.get_by_group_name('hello'), new_sg1)
 

	
 
    def test_subgrouping_with_repo(self):
 

	
 
        g1 = _make_group('g1')
 
        g2 = _make_group('g2')
 

	
 
        # create new repo
 
        form_data = dict(repo_name='john',
 
                         repo_name_full='john',
 
                         fork_name=None,
 
                         description=None,
 
                         repo_group=None,
 
                         private=False,
 
                         repo_type='hg',
 
                         clone_uri=None)
 
        cur_user = User.get_by_username(TEST_USER_ADMIN_LOGIN)
 
        r = RepoModel().create(form_data, cur_user)
 

	
 
        self.assertEqual(r.repo_name, 'john')
 

	
 
        # put repo into group
 
        form_data = form_data
 
        form_data['repo_group'] = g1.group_id
 
        form_data['perms_new'] = []
 
        form_data['perms_updates'] = []
 
        RepoModel().update(r.repo_name, form_data)
 
        self.assertEqual(r.repo_name, 'g1/john')
 

	
 
        self.__update_group(g1.group_id, 'g1', parent_id=g2.group_id)
 
        self.assertTrue(self.__check_path('g2', 'g1'))
 

	
 
        # test repo
 
        self.assertEqual(r.repo_name, os.path.join('g2', 'g1', r.just_name))
 

	
 
        self.assertEqual(r.repo_name, RepoGroup.url_sep().join(['g2', 'g1', r.just_name]))
 

	
 
    def test_move_to_root(self):
 
        g1 = _make_group('t11')
 
        Session.commit()
 
        g2 = _make_group('t22',parent_id=g1.group_id)
 
        Session.commit()
 

	
 
        self.assertEqual(g2.full_path,'t11/t22')
 
        self.assertTrue(self.__check_path('t11', 't22'))
 

	
 
        g2 = self.__update_group(g2.group_id, 'g22', parent_id=None)
 
        Session.commit()
 

	
 
        self.assertEqual(g2.group_name,'g22')
 
        # we moved out group from t1 to '' so it's full path should be 'g2'
 
        self.assertEqual(g2.full_path,'g22')
 
        self.assertFalse(self.__check_path('t11', 't22'))
 
        self.assertTrue(self.__check_path('g22'))
 

	
 

	
 
class TestUser(unittest.TestCase):
 
    def __init__(self, methodName='runTest'):
 
        Session.remove()
 
        super(TestUser, self).__init__(methodName=methodName)
 

	
 
    def test_create_and_remove(self):
 
        usr = UserModel().create_or_update(username=u'test_user', password=u'qweqwe',
 
                                     email=u'u232@rhodecode.org',
 
                                     name=u'u1', lastname=u'u1')
 
        Session.commit()
 
        self.assertEqual(User.get_by_username(u'test_user'), usr)
 

	
 
        # make users group
 
        users_group = UsersGroupModel().create('some_example_group')
 
        Session.commit()
 

	
 
        UsersGroupModel().add_user_to_group(users_group, usr)
 
        Session.commit()
 

	
 
        self.assertEqual(UsersGroup.get(users_group.users_group_id), users_group)
 
        self.assertEqual(UsersGroupMember.query().count(), 1)
 
        UserModel().delete(usr.user_id)
 
        Session.commit()
 

	
 
        self.assertEqual(UsersGroupMember.query().all(), [])
 

	
 

	
 
class TestNotifications(unittest.TestCase):
 

	
 
    def __init__(self, methodName='runTest'):
 
        Session.remove()
 
        self.u1 = UserModel().create_or_update(username=u'u1',
 
                                        password=u'qweqwe',
 
                                        email=u'u1@rhodecode.org',
 
                                        name=u'u1', lastname=u'u1')
 
        Session.commit()
 
        self.u1 = self.u1.user_id
 

	
 
        self.u2 = UserModel().create_or_update(username=u'u2',
 
                                        password=u'qweqwe',
 
                                        email=u'u2@rhodecode.org',
 
                                        name=u'u2', lastname=u'u3')
 
        Session.commit()
 
        self.u2 = self.u2.user_id
 

	
 
        self.u3 = UserModel().create_or_update(username=u'u3',
 
                                        password=u'qweqwe',
 
                                        email=u'u3@rhodecode.org',
 
                                        name=u'u3', lastname=u'u3')
 
        Session.commit()
 
        self.u3 = self.u3.user_id
 

	
 
        super(TestNotifications, self).__init__(methodName=methodName)
 

	
 
    def _clean_notifications(self):
 
        for n in Notification.query().all():
 
            Session.delete(n)
 

	
 
        Session.commit()
 
        self.assertEqual(Notification.query().all(), [])
 

	
 
    def tearDown(self):
 
        self._clean_notifications()
 

	
 
    def test_create_notification(self):
 
        self.assertEqual([], Notification.query().all())
 
        self.assertEqual([], UserNotification.query().all())
 

	
 
        usrs = [self.u1, self.u2]
 
        notification = NotificationModel().create(created_by=self.u1,
 
                                           subject=u'subj', body=u'hi there',
 
                                           recipients=usrs)
 
        Session.commit()
 
        u1 = User.get(self.u1)
 
        u2 = User.get(self.u2)
 
        u3 = User.get(self.u3)
 
@@ -527,189 +527,189 @@ class TestPermissions(unittest.TestCase)
 
        # add user to group
 
        UsersGroupModel().add_user_to_group(self.ug1, self.u1)
 

	
 
        # set permission to lower
 
        new_perm = 'repository.none'
 
        RepoModel().grant_user_permission(repo=HG_REPO, user=self.u1, perm=new_perm)
 
        Session.commit()
 
        u1_auth = AuthUser(user_id=self.u1.user_id)
 
        self.assertEqual(u1_auth.permissions['repositories'][HG_REPO],
 
                         new_perm)
 

	
 
        # grant perm for group this should override permission from user
 
        new_perm = 'repository.write'
 
        RepoModel().grant_users_group_permission(repo=HG_REPO,
 
                                                 group_name=self.ug1,
 
                                                 perm=new_perm)
 
        # check perms
 
        u1_auth = AuthUser(user_id=self.u1.user_id)
 
        perms = {
 
            'repositories_groups': {},
 
            'global': set([u'hg.create.repository', u'repository.read',
 
                           u'hg.register.manual_activate']),
 
            'repositories': {u'vcs_test_hg': u'repository.read'}
 
        }
 
        self.assertEqual(u1_auth.permissions['repositories'][HG_REPO],
 
                         new_perm)
 
        self.assertEqual(u1_auth.permissions['repositories_groups'],
 
                         perms['repositories_groups'])
 

	
 
    def test_propagated_permission_from_users_group_lower_weight(self):
 
        # make group
 
        self.ug1 = UsersGroupModel().create('G1')
 
        # add user to group
 
        UsersGroupModel().add_user_to_group(self.ug1, self.u1)
 

	
 
        # set permission to lower
 
        new_perm_h = 'repository.write'
 
        RepoModel().grant_user_permission(repo=HG_REPO, user=self.u1,
 
                                          perm=new_perm_h)
 
        Session.commit()
 
        u1_auth = AuthUser(user_id=self.u1.user_id)
 
        self.assertEqual(u1_auth.permissions['repositories'][HG_REPO],
 
                         new_perm_h)
 

	
 
        # grant perm for group this should NOT override permission from user
 
        # since it's lower than granted
 
        new_perm_l = 'repository.read'
 
        RepoModel().grant_users_group_permission(repo=HG_REPO,
 
                                                 group_name=self.ug1,
 
                                                 perm=new_perm_l)
 
        # check perms
 
        u1_auth = AuthUser(user_id=self.u1.user_id)
 
        perms = {
 
            'repositories_groups': {},
 
            'global': set([u'hg.create.repository', u'repository.read',
 
                           u'hg.register.manual_activate']),
 
            'repositories': {u'vcs_test_hg': u'repository.write'}
 
        }
 
        self.assertEqual(u1_auth.permissions['repositories'][HG_REPO],
 
                         new_perm_h)
 
        self.assertEqual(u1_auth.permissions['repositories_groups'],
 
                         perms['repositories_groups'])
 

	
 
    def test_repo_in_group_permissions(self):
 
        self.g1 = _make_group('group1', skip_if_exists=True)
 
        self.g2 = _make_group('group2', skip_if_exists=True)
 
        Session.commit()
 
        # both perms should be read !
 
        u1_auth = AuthUser(user_id=self.u1.user_id)
 
        self.assertEqual(u1_auth.permissions['repositories_groups'],
 
                         {u'group1': u'group.read', u'group2': u'group.read'})
 

	
 
        a1_auth = AuthUser(user_id=self.anon.user_id)
 
        self.assertEqual(a1_auth.permissions['repositories_groups'],
 
                 {u'group1': u'group.read', u'group2': u'group.read'})
 

	
 
        #Change perms to none for both groups
 
        ReposGroupModel().grant_user_permission(repos_group=self.g1,
 
                                                user=self.anon,
 
                                                perm='group.none')
 
        ReposGroupModel().grant_user_permission(repos_group=self.g2,
 
                                                user=self.anon,
 
                                                perm='group.none')
 

	
 

	
 
        u1_auth = AuthUser(user_id=self.u1.user_id)
 
        self.assertEqual(u1_auth.permissions['repositories_groups'],
 
                 {u'group1': u'group.none', u'group2': u'group.none'})
 

	
 
        a1_auth = AuthUser(user_id=self.anon.user_id)
 
        self.assertEqual(a1_auth.permissions['repositories_groups'],
 
                 {u'group1': u'group.none', u'group2': u'group.none'})
 

	
 
        # add repo to group
 
        form_data = {
 
            'repo_name':HG_REPO,
 
            'repo_name_full':os.path.join(self.g1.group_name,HG_REPO),
 
            'repo_name_full':RepoGroup.url_sep().join([self.g1.group_name,HG_REPO]),
 
            'repo_type':'hg',
 
            'clone_uri':'',
 
            'repo_group':self.g1.group_id,
 
            'description':'desc',
 
            'private':False
 
        }
 
        self.test_repo = RepoModel().create(form_data, cur_user=self.u1)
 
        Session.commit()
 

	
 
        u1_auth = AuthUser(user_id=self.u1.user_id)
 
        self.assertEqual(u1_auth.permissions['repositories_groups'],
 
                 {u'group1': u'group.none', u'group2': u'group.none'})
 

	
 
        a1_auth = AuthUser(user_id=self.anon.user_id)
 
        self.assertEqual(a1_auth.permissions['repositories_groups'],
 
                 {u'group1': u'group.none', u'group2': u'group.none'})
 

	
 
        #grant permission for u2 !
 
        ReposGroupModel().grant_user_permission(repos_group=self.g1,
 
                                                user=self.u2,
 
                                                perm='group.read')
 
        ReposGroupModel().grant_user_permission(repos_group=self.g2,
 
                                                user=self.u2,
 
                                                perm='group.read')
 
        Session.commit()
 
        self.assertNotEqual(self.u1, self.u2)
 
        #u1 and anon should have not change perms while u2 should !
 
        u1_auth = AuthUser(user_id=self.u1.user_id)
 
        self.assertEqual(u1_auth.permissions['repositories_groups'],
 
                 {u'group1': u'group.none', u'group2': u'group.none'})
 

	
 
        u2_auth = AuthUser(user_id=self.u2.user_id)
 
        self.assertEqual(u2_auth.permissions['repositories_groups'],
 
                 {u'group1': u'group.read', u'group2': u'group.read'})
 

	
 
        a1_auth = AuthUser(user_id=self.anon.user_id)
 
        self.assertEqual(a1_auth.permissions['repositories_groups'],
 
                 {u'group1': u'group.none', u'group2': u'group.none'})
 

	
 
    def test_repo_group_user_as_user_group_member(self):
 
        # create Group1
 
        self.g1 = _make_group('group1', skip_if_exists=True)
 
        Session.commit()
 
        a1_auth = AuthUser(user_id=self.anon.user_id)
 

	
 
        self.assertEqual(a1_auth.permissions['repositories_groups'],
 
                         {u'group1': u'group.read'})
 

	
 
        # set default permission to none
 
        ReposGroupModel().grant_user_permission(repos_group=self.g1,
 
                                                user=self.anon,
 
                                                perm='group.none')
 
        # make group
 
        self.ug1 = UsersGroupModel().create('G1')
 
        # add user to group
 
        UsersGroupModel().add_user_to_group(self.ug1, self.u1)
 
        Session.commit()
 

	
 
        # check if user is in the group
 
        membrs = [x.user_id for x in UsersGroupModel().get(self.ug1.users_group_id).members]
 
        self.assertEqual(membrs, [self.u1.user_id])
 
        # add some user to that group
 

	
 
        # check his permissions
 
        a1_auth = AuthUser(user_id=self.anon.user_id)
 
        self.assertEqual(a1_auth.permissions['repositories_groups'],
 
                         {u'group1': u'group.none'})
 

	
 
        u1_auth = AuthUser(user_id=self.u1.user_id)
 
        self.assertEqual(u1_auth.permissions['repositories_groups'],
 
                         {u'group1': u'group.none'})
 

	
 
        # grant ug1 read permissions for
 
        ReposGroupModel().grant_users_group_permission(repos_group=self.g1,
 
                                                       group_name=self.ug1,
 
                                                       perm='group.read')
 
        Session.commit()
 
        # check if the
 
        obj = Session.query(UsersGroupRepoGroupToPerm)\
 
            .filter(UsersGroupRepoGroupToPerm.group == self.g1)\
 
            .filter(UsersGroupRepoGroupToPerm.users_group == self.ug1)\
 
            .scalar()
 
        self.assertEqual(obj.permission.permission_name, 'group.read')
 

	
 
        a1_auth = AuthUser(user_id=self.anon.user_id)
 

	
 
        self.assertEqual(a1_auth.permissions['repositories_groups'],
 
                         {u'group1': u'group.none'})
 

	
 
        u1_auth = AuthUser(user_id=self.u1.user_id)
 
        self.assertEqual(u1_auth.permissions['repositories_groups'],
 
                         {u'group1': u'group.read'})
0 comments (0 inline, 0 general)