Mercurial > kallithea
changeset 2257:a437a986d399
merged beta into stable
line wrap: on
line diff
--- a/docs/changelog.rst Mon Apr 23 18:31:51 2012 +0200 +++ b/docs/changelog.rst Thu May 10 20:27:45 2012 +0200 @@ -4,6 +4,39 @@ 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**) ----------------------
--- a/docs/conf.py Mon Apr 23 18:31:51 2012 +0200 +++ b/docs/conf.py Thu May 10 20:27:45 2012 +0200 @@ -11,7 +11,9 @@ # 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 @@ -25,7 +27,9 @@ # 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'] @@ -41,7 +45,7 @@ # 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
--- a/rhodecode/__init__.py Mon Apr 23 18:31:51 2012 +0200 +++ b/rhodecode/__init__.py Thu May 10 20:27:45 2012 +0200 @@ -26,7 +26,7 @@ import sys import platform -VERSION = (1, 3, 4) +VERSION = (1, 3, 5) try: from rhodecode.lib import get_current_revision @@ -46,19 +46,22 @@ 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", @@ -68,11 +71,11 @@ 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():
--- a/rhodecode/config/routing.py Mon Apr 23 18:31:51 2012 +0200 +++ b/rhodecode/config/routing.py Thu May 10 20:27:45 2012 +0200 @@ -454,8 +454,8 @@ 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:.*}',
--- a/rhodecode/controllers/admin/ldap_settings.py Mon Apr 23 18:31:51 2012 +0200 +++ b/rhodecode/controllers/admin/ldap_settings.py Thu May 10 20:27:45 2012 +0200 @@ -100,25 +100,37 @@ _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') + 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 {}
--- a/rhodecode/controllers/admin/notifications.py Mon Apr 23 18:31:51 2012 +0200 +++ b/rhodecode/controllers/admin/notifications.py Thu May 10 20:27:45 2012 +0200 @@ -30,6 +30,8 @@ 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 @@ -58,8 +60,9 @@ """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): @@ -69,7 +72,8 @@ 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):
--- a/rhodecode/controllers/admin/settings.py Mon Apr 23 18:31:51 2012 +0200 +++ b/rhodecode/controllers/admin/settings.py Thu May 10 20:27:45 2012 +0200 @@ -26,6 +26,8 @@ import logging import traceback import formencode +import pkg_resources +import platform from sqlalchemy import func from formencode import htmlfill @@ -64,6 +66,11 @@ 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') @@ -73,6 +80,7 @@ defaults = RhodeCodeSetting.get_app_settings() defaults.update(self.get_hg_ui_settings()) + return htmlfill.render( render('admin/settings/settings.html'), defaults=defaults,
--- a/rhodecode/controllers/changelog.py Mon Apr 23 18:31:51 2012 +0200 +++ b/rhodecode/controllers/changelog.py Thu May 10 20:27:45 2012 +0200 @@ -125,7 +125,8 @@ 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
--- a/rhodecode/controllers/changeset.py Mon Apr 23 18:31:51 2012 +0200 +++ b/rhodecode/controllers/changeset.py Thu May 10 20:27:45 2012 +0200 @@ -359,16 +359,31 @@ return render('changeset/raw_changeset.html') + @jsonify def comment(self, repo_name, revision): - 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')) + 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') + ) Session.commit() - return redirect(h.url('changeset_home', repo_name=repo_name, - revision=revision)) + 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):
--- a/rhodecode/controllers/files.py Mon Apr 23 18:31:51 2012 +0200 +++ b/rhodecode/controllers/files.py Thu May 10 20:27:45 2012 +0200 @@ -48,6 +48,7 @@ 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 @@ -112,7 +113,7 @@ @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: @@ -123,7 +124,7 @@ 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 @@ -168,7 +169,7 @@ 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 @@ -219,16 +220,6 @@ 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 @@ -316,10 +307,10 @@ 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) + 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:
--- a/rhodecode/controllers/forks.py Mon Apr 23 18:31:51 2012 +0200 +++ b/rhodecode/controllers/forks.py Thu May 10 20:27:45 2012 +0200 @@ -35,7 +35,7 @@ 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 @@ -103,7 +103,13 @@ 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')
--- a/rhodecode/controllers/summary.py Mon Apr 23 18:31:51 2012 +0200 +++ b/rhodecode/controllers/summary.py Thu May 10 20:27:45 2012 +0200 @@ -179,10 +179,12 @@ 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): @@ -190,7 +192,7 @@ 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: @@ -202,6 +204,7 @@ except NodeDoesNotExistError: continue except ChangesetError: + log.error(traceback.format_exc()) pass except EmptyRepositoryError: pass @@ -210,7 +213,7 @@ 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)
--- a/rhodecode/lib/base.py Mon Apr 23 18:31:51 2012 +0200 +++ b/rhodecode/lib/base.py Thu May 10 20:27:45 2012 +0200 @@ -116,6 +116,17 @@ 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:
--- a/rhodecode/lib/celerylib/tasks.py Mon Apr 23 18:31:51 2012 +0200 +++ b/rhodecode/lib/celerylib/tasks.py Thu May 10 20:27:45 2012 +0200 @@ -47,6 +47,7 @@ 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 @@ -372,7 +373,8 @@ 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'] @@ -387,6 +389,8 @@ 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) @@ -395,6 +399,7 @@ # 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
--- a/rhodecode/lib/compat.py Mon Apr 23 18:31:51 2012 +0200 +++ b/rhodecode/lib/compat.py Thu May 10 20:27:45 2012 +0200 @@ -25,92 +25,12 @@ # 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 #==============================================================================
--- a/rhodecode/lib/dbmigrate/versions/003_version_1_2_0.py Mon Apr 23 18:31:51 2012 +0200 +++ b/rhodecode/lib/dbmigrate/versions/003_version_1_2_0.py Thu May 10 20:27:45 2012 +0200 @@ -71,7 +71,6 @@ is_ldap = Column("is_ldap", Boolean(), nullable=False, unique=None, default=False) is_ldap.drop(User().__table__) - #========================================================================== # Upgrade of `repositories` table #========================================================================== @@ -100,7 +99,6 @@ group_id.create(Repository().__table__) - #========================================================================== # Upgrade of `user_followings` table #==========================================================================
--- a/rhodecode/lib/diffs.py Mon Apr 23 18:31:51 2012 +0200 +++ b/rhodecode/lib/diffs.py Thu May 10 20:27:45 2012 +0200 @@ -33,8 +33,8 @@ 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 @@ -79,9 +79,13 @@ 'diff menu to display this diff')) stats = (0, 0) size = 0 - if not diff: - diff = wrap_to_table(_('No changes detected')) + 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 @@ -97,6 +101,10 @@ """ # 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): @@ -109,7 +117,6 @@ vcs_gitdiff = repo.get_diff(old_raw_id, new_raw_id, filenode_new.path, ignore_whitespace, context) - return vcs_gitdiff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/lib/ext_json.py Thu May 10 20:27:45 2012 +0200 @@ -0,0 +1,104 @@ +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
--- a/rhodecode/lib/helpers.py Mon Apr 23 18:31:51 2012 +0200 +++ b/rhodecode/lib/helpers.py Thu May 10 20:27:45 2012 +0200 @@ -87,7 +87,7 @@ if not token_key in session: try: token = hashlib.sha1(str(random.getrandbits(128))).hexdigest() - except AttributeError: # Python < 2.4 + except AttributeError: # Python < 2.4 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest() session[token_key] = token if hasattr(session, 'save'): @@ -454,11 +454,14 @@ revision=rev.raw_id), title=tooltip(message(rev)), class_='tooltip') ) - # 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]) - ] + + 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(
--- a/rhodecode/lib/hooks.py Mon Apr 23 18:31:51 2012 +0200 +++ b/rhodecode/lib/hooks.py Thu May 10 20:27:45 2012 +0200 @@ -33,6 +33,33 @@ 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 @@ -42,24 +69,7 @@ :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] @@ -82,6 +92,7 @@ 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) @@ -100,28 +111,33 @@ 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' - node = kwargs['node'] + scm = extras['scm'] - def get_revs(repo, rev_opt): - if rev_opt: - revs = revrange(repo, rev_opt) + 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) + if len(revs) == 0: + return (nullrev, nullrev) + return (max(revs), min(revs)) + else: + return (len(repo) - 1, 0) - stop, start = get_revs(repo, [node + ':']) + stop, start = get_revs(repo, [node + ':']) - revs = (str(repo[r]) for r in xrange(start, stop + 1)) + revs = (str(repo[r]) for r in xrange(start, stop + 1)) + elif scm == 'git': + revs = [] action = action % ','.join(revs)
--- a/rhodecode/lib/markup_renderer.py Mon Apr 23 18:31:51 2012 +0200 +++ b/rhodecode/lib/markup_renderer.py Thu May 10 20:27:45 2012 +0200 @@ -27,7 +27,7 @@ import re import logging -from rhodecode.lib.utils2 import safe_unicode +from rhodecode.lib.utils2 import safe_unicode, MENTIONS_REGEX log = logging.getLogger(__name__) @@ -128,10 +128,10 @@ @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} + return ' **@%(uname)s** ' % {'uname': uname} mention_hl = mention_pat.sub(wrapp, source).strip() return cls.rst(mention_hl)
--- a/rhodecode/lib/middleware/simplegit.py Mon Apr 23 18:31:51 2012 +0200 +++ b/rhodecode/lib/middleware/simplegit.py Thu May 10 20:27:45 2012 +0200 @@ -44,13 +44,14 @@ 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') @@ -59,6 +60,7 @@ # we are done self.proto.write("0000") + dulserver.DEFAULT_HANDLERS = { 'git-upload-pack': SimpleGitUploadPackHandler, 'git-receive-pack': dulserver.ReceivePackHandler, @@ -72,7 +74,7 @@ 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 @@ -99,10 +101,9 @@ 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 @@ -178,6 +179,13 @@ 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 @@ -185,10 +193,16 @@ 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 + # 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) @@ -249,3 +263,38 @@ # 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)
--- a/rhodecode/lib/middleware/simplehg.py Mon Apr 23 18:31:51 2012 +0200 +++ b/rhodecode/lib/middleware/simplehg.py Thu May 10 20:27:45 2012 +0200 @@ -69,9 +69,7 @@ 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 @@ -155,7 +153,8 @@ 'ip': ipaddr, 'username': username, 'action': action, - 'repository': repo_name + 'repository': repo_name, + 'scm': 'hg', } #======================================================================
--- a/rhodecode/lib/utils.py Mon Apr 23 18:31:51 2012 +0200 +++ b/rhodecode/lib/utils.py Thu May 10 20:27:45 2012 +0200 @@ -150,7 +150,7 @@ 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
--- a/rhodecode/lib/utils2.py Mon Apr 23 18:31:51 2012 +0200 +++ b/rhodecode/lib/utils2.py Thu May 10 20:27:45 2012 +0200 @@ -392,14 +392,17 @@ 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())
--- a/rhodecode/lib/vcs/backends/base.py Mon Apr 23 18:31:51 2012 +0200 +++ b/rhodecode/lib/vcs/backends/base.py Thu May 10 20:27:45 2012 +0200 @@ -909,3 +909,48 @@ :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
--- a/rhodecode/lib/vcs/backends/git/changeset.py Mon Apr 23 18:31:51 2012 +0200 +++ b/rhodecode/lib/vcs/backends/git/changeset.py Thu May 10 20:27:45 2012 +0200 @@ -10,7 +10,8 @@ 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 @@ -66,26 +67,12 @@ @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): """ @@ -144,7 +131,6 @@ 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" @@ -344,7 +330,13 @@ 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)) @@ -372,24 +364,31 @@ 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) - if isinstance(obj, objects.Tree): - if path == '': - node = RootNode(changeset=self) + + 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: - 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)) + 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] @@ -406,7 +405,7 @@ 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) @@ -422,13 +421,15 @@ for line in self._diff_name_status.splitlines(): if not line: continue + if line.startswith(char): - splitted = line.split(char,1) + 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
--- a/rhodecode/lib/vcs/backends/git/inmemory.py Mon Apr 23 18:31:51 2012 +0200 +++ b/rhodecode/lib/vcs/backends/git/inmemory.py Thu May 10 20:27:45 2012 +0200 @@ -5,12 +5,13 @@ 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): + **kwargs): """ Performs in-memory commit (doesn't check workdir in any way) and returns newly created ``Changeset``. Updates repository's @@ -120,9 +121,9 @@ 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:
--- a/rhodecode/lib/vcs/backends/git/repository.py Mon Apr 23 18:31:51 2012 +0200 +++ b/rhodecode/lib/vcs/backends/git/repository.py Thu May 10 20:27:45 2012 +0200 @@ -47,6 +47,15 @@ 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: @@ -78,11 +87,16 @@ :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), @@ -245,6 +259,19 @@ 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 {} @@ -384,7 +411,7 @@ yield self.get_changeset(rev) def get_diff(self, rev1, rev2, path=None, ignore_whitespace=False, - context=3): + context=3): """ Returns (git like) *diff*, as plain text. Shows changes introduced by ``rev2`` since ``rev1``. @@ -453,6 +480,18 @@ # 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): """
--- a/rhodecode/lib/vcs/backends/hg/changeset.py Mon Apr 23 18:31:51 2012 +0200 +++ b/rhodecode/lib/vcs/backends/hg/changeset.py Thu May 10 20:27:45 2012 +0200 @@ -5,8 +5,9 @@ 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 @@ -36,6 +37,10 @@ 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()) @@ -159,6 +164,13 @@ " %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``. @@ -271,17 +283,27 @@ 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):
--- a/rhodecode/lib/vcs/backends/hg/inmemory.py Mon Apr 23 18:31:51 2012 +0200 +++ b/rhodecode/lib/vcs/backends/hg/inmemory.py Thu May 10 20:27:45 2012 +0200 @@ -4,7 +4,7 @@ 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): @@ -30,9 +30,9 @@ 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 @@ -70,7 +70,7 @@ copied=False) raise RepositoryError("Given path haven't been marked as added," - "changed or removed (%s)" % path) + "changed or removed (%s)" % path) parents = [None, None] for i, parent in enumerate(self.parents): @@ -89,9 +89,11 @@ 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!
--- a/rhodecode/lib/vcs/nodes.py Mon Apr 23 18:31:51 2012 +0200 +++ b/rhodecode/lib/vcs/nodes.py Thu May 10 20:27:45 2012 +0200 @@ -8,19 +8,22 @@ :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 @@ -120,6 +123,10 @@ 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 @@ -205,6 +212,13 @@ """ 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 @@ -557,3 +571,41 @@ 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)
--- a/rhodecode/lib/vcs/utils/hgcompat.py Mon Apr 23 18:31:51 2012 +0200 +++ b/rhodecode/lib/vcs/utils/hgcompat.py Thu May 10 20:27:45 2012 +0200 @@ -1,6 +1,7 @@ -"""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 @@ -10,3 +11,4 @@ from mercurial.match import match from mercurial.mdiff import diffopts from mercurial.node import hex +from mercurial.encoding import tolocal
--- a/rhodecode/model/comment.py Mon Apr 23 18:31:51 2012 +0200 +++ b/rhodecode/model/comment.py Thu May 10 20:27:45 2012 +0200 @@ -29,7 +29,7 @@ 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 @@ -67,7 +67,7 @@ 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 @@ -83,14 +83,17 @@ line = '' if line_no: line = _('on line %s') % line_no - subj = 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, - ) - ) + 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 @@ -139,7 +142,9 @@ .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))
--- a/rhodecode/model/db.py Mon Apr 23 18:31:51 2012 +0200 +++ b/rhodecode/model/db.py Thu May 10 20:27:45 2012 +0200 @@ -645,7 +645,7 @@ # SCM PROPERTIES #========================================================================== - def get_changeset(self, rev): + def get_changeset(self, rev=None): return get_changeset_safe(self.scm_instance, rev) @property @@ -1233,7 +1233,8 @@ @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):
--- a/rhodecode/model/forms.py Mon Apr 23 18:31:51 2012 +0200 +++ b/rhodecode/model/forms.py Thu May 10 20:27:45 2012 +0200 @@ -551,7 +551,7 @@ ) password = UnicodeString( - strip=True, + strip=False, min=3, not_empty=True, messages={ @@ -571,13 +571,13 @@ 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) @@ -632,8 +632,8 @@ 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) @@ -754,7 +754,7 @@ 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,)
--- a/rhodecode/model/repo.py Mon Apr 23 18:31:51 2012 +0200 +++ b/rhodecode/model/repo.py Thu May 10 20:27:45 2012 +0200 @@ -286,12 +286,12 @@ 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())
--- a/rhodecode/model/scm.py Mon Apr 23 18:31:51 2012 +0200 +++ b/rhodecode/model/scm.py Thu May 10 20:27:45 2012 +0200 @@ -35,7 +35,7 @@ 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 @@ -343,12 +343,15 @@ repo = dbrepo.scm_instance try: - extras = {'ip': '', - 'username': username, - 'action': 'push_remote', - 'repository': repo_name} + extras = { + 'ip': '', + 'username': username, + 'action': 'push_remote', + 'repository': repo_name, + 'scm': repo.alias, + } - #inject ui extra param to log this action via push logger + # 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) @@ -362,21 +365,25 @@ 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) + author=author, + parents=[cs], branch=cs.branch) new_cs = tip.short_id action = 'push_local:%s' % new_cs @@ -403,21 +410,21 @@ 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) + author=author, + parents=parents, branch=cs.branch) new_cs = tip.short_id action = 'push_local:%s' % new_cs
--- a/rhodecode/model/user.py Mon Apr 23 18:31:51 2012 +0200 +++ b/rhodecode/model/user.py Thu May 10 20:27:45 2012 +0200 @@ -225,10 +225,8 @@ 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() @@ -398,145 +396,148 @@ 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 + #================================================================== + # 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) + # 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) + 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 + # 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 - 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) - # 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 + # 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() - #================================================================== - # overwrite defaults with user permissions if any found - #================================================================== + 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 global permissions - user_perms = self.sa.query(UserToPerm)\ - .options(joinedload(UserToPerm.permission))\ - .filter(UserToPerm.user_id == uid).all() + # USER GROUP + #================================================================== + # check if user is part of user groups for this repository and + # fill in (or replace with higher) permissions + #================================================================== - for perm in user_perms: - user.permissions[GLOBAL].add(perm.permission.permission_name) + # 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) - # 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() + # 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: - # 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 + 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 - #================================================================== - # 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) + # REPO GROUP + #================================================================== + # get access for this user for repos group and override defaults + #================================================================== - # 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() + # 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_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 - - #================================================================== - # get access for this user for repos group and override defaults - #================================================================== + 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 - # 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 + #================================================================== - #================================================================== - # 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() - # 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 + 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
--- a/rhodecode/public/css/style.css Mon Apr 23 18:31:51 2012 +0200 +++ b/rhodecode/public/css/style.css Thu May 10 20:27:45 2012 +0200 @@ -2520,6 +2520,10 @@ .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; @@ -2558,10 +2562,10 @@ 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; @@ -2570,10 +2574,10 @@ -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; } @@ -2718,6 +2722,14 @@ 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; @@ -3966,6 +3978,7 @@ .comment .buttons { float: right; + padding:2px 2px 0px 0px; } @@ -3975,6 +3988,23 @@ } /** 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; @@ -3987,6 +4017,7 @@ div.comment-inline-form { margin-top: 5px; padding:2px 6px 8px 6px; + } .comment-inline-form strong { @@ -4047,6 +4078,10 @@ margin: 3px 3px 5px 5px; background-color: #FAFAFA; } +.inline-comments .add-comment { + padding: 2px 4px 8px 5px; +} + .inline-comments .comment-wrapp{ padding:1px; } @@ -4078,8 +4113,15 @@ 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; @@ -4113,16 +4155,24 @@ 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; @@ -4134,6 +4184,11 @@ padding:5px 0px 5px 38px; } +.notification-body{ + clear:both; + margin: 34px 2px 2px 8px +} + /**** PERMS *****/
--- a/rhodecode/public/js/rhodecode.js Mon Apr 23 18:31:51 2012 +0200 +++ b/rhodecode/public/js/rhodecode.js Thu May 10 20:27:45 2012 +0200 @@ -195,6 +195,34 @@ }; +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 */ @@ -300,33 +328,25 @@ } }; -var ajaxPOST = function(url,postData,success) { - var sUrl = url; - var callback = { - success: success, - failure: function (o) { - alert("error"); - }, - }; - var postData = postData; - var request = YAHOO.util.Connect.asyncRequest('POST', sUrl, callback, postData); +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; }; - /** 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); @@ -337,12 +357,27 @@ 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 } @@ -350,20 +385,96 @@ 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); - } - YUD.insertAfter(form,target_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,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 add = new YAHOO.util.Element(tableTr('inline-comments-button',html)); +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); + + // 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); }); @@ -384,6 +495,103 @@ 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){
--- a/rhodecode/templates/admin/notifications/notifications.html Mon Apr 23 18:31:51 2012 +0200 +++ b/rhodecode/templates/admin/notifications/notifications.html Thu May 10 20:27:45 2012 +0200 @@ -25,7 +25,7 @@ ##</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 @@ -39,15 +39,18 @@ 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; - 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(){ + 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>
--- a/rhodecode/templates/admin/notifications/notifications_data.html Mon Apr 23 18:31:51 2012 +0200 +++ b/rhodecode/templates/admin/notifications/notifications_data.html Thu May 10 20:27:45 2012 +0200 @@ -3,26 +3,37 @@ <% unread = lambda n:{False:'unread'}.get(n) %> -<div class="table"> - <div class="notification-list"> - %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 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
--- a/rhodecode/templates/admin/notifications/show_notification.html Mon Apr 23 18:31:51 2012 +0200 +++ b/rhodecode/templates/admin/notifications/show_notification.html Thu May 10 20:27:45 2012 +0200 @@ -39,7 +39,7 @@ <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>
--- a/rhodecode/templates/admin/settings/settings.html Mon Apr 23 18:31:51 2012 +0200 +++ b/rhodecode/templates/admin/settings/settings.html Thu May 10 20:27:45 2012 +0200 @@ -210,5 +210,37 @@ </div> ${h.end_form()} + <h3>${_('System Info and Packages')}</h3> + <div class="form"> + <div> + <h5 id="expand_modules" style="cursor: pointer">↓ ${_('show')} ↓</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>
--- a/rhodecode/templates/base/root.html Mon Apr 23 18:31:51 2012 +0200 +++ b/rhodecode/templates/base/root.html Thu May 10 20:27:45 2012 +0200 @@ -47,9 +47,13 @@ <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); @@ -57,7 +61,7 @@ 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; @@ -66,7 +70,7 @@ } 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; @@ -133,13 +137,13 @@ ## IE hacks <!--[if IE 7]> <script>YUD.addClass(document.body,'ie7')</script> - <![endif]--> + <![endif]--> <!--[if IE 8]> <script>YUD.addClass(document.body,'ie8')</script> <![endif]--> <!--[if IE 9]> <script>YUD.addClass(document.body,'ie9')</script> - <![endif]--> + <![endif]--> ${next.body()} </body>
--- a/rhodecode/templates/changelog/changelog.html Mon Apr 23 18:31:51 2012 +0200 +++ b/rhodecode/templates/changelog/changelog.html Thu May 10 20:27:45 2012 +0200 @@ -63,7 +63,7 @@ <div class="expand"><span class="expandtext">↓ ${_('show more')} ↓</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: @@ -91,10 +91,18 @@ %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> @@ -177,7 +185,7 @@ 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
--- a/rhodecode/templates/changeset/changeset.html Mon Apr 23 18:31:51 2012 +0200 +++ b/rhodecode/templates/changeset/changeset.html Thu May 10 20:27:45 2012 +0200 @@ -81,8 +81,11 @@ %if len(c.changeset.parents)>1: <span class="merge">${_('merge')}</span> %endif - <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> + %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> + %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> @@ -122,22 +125,12 @@ <%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; @@ -162,28 +155,7 @@ // 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>
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/rhodecode/templates/changeset/changeset_comment_block.html Thu May 10 20:27:45 2012 +0200 @@ -0,0 +1,2 @@ +<%namespace name="comment" file="/changeset/changeset_file_comment.html"/> +${comment.comment_block(c.co)}
--- a/rhodecode/templates/changeset/changeset_file_comment.html Mon Apr 23 18:31:51 2012 +0200 +++ b/rhodecode/templates/changeset/changeset_file_comment.html Thu May 10 20:27:45 2012 +0200 @@ -4,7 +4,7 @@ ## ${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"> @@ -32,7 +32,8 @@ <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')} @@ -43,7 +44,7 @@ <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()} @@ -64,25 +65,31 @@ </%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: - ${comment_block(co)} + <div id="comment-tr-${co.comment_id}"> + ${comment_block(co)} + </div> %endfor %if c.rhodecode_user.username != 'default': <div class="comment-form">
--- a/rhodecode/templates/files/files_annotate.html Mon Apr 23 18:31:51 2012 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,136 +0,0 @@ -<%inherit file="/base/base.html"/> - -<%def name="title()"> - ${c.repo_name} ${_('File annotate')} - ${c.rhodecode_name} -</%def> - -<%def name="breadcrumbs_links()"> - ${h.link_to(u'Home',h.url('/'))} - » - ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))} - » - ${_('annotate')} @ 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> - <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 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 source'),h.url('files_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(_('edit'),h.url('files_edit_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="author"> - <div class="gravatar"> - <img alt="gravatar" src="${h.gravatar_url(h.email(c.cs.author),16)}"/> - </div> - <div title="${c.cs.author}" class="user">${h.person(c.cs.author)}</div> - </div> - <div class="commit">${c.file.last_changeset.message}</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: - ${h.pygmentize_annotation(c.repo_name,c.file,linenos=True,anchorlinenos=True,lineanchors='L',cssclass="code-highlight")} - %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.cs.revision,f_path=c.f_path))} - %endif - <script type="text/javascript"> - 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"> - YAHOO.util.Event.onDOMReady(function(){ - YUE.on('show_rev','click',function(e){ - YAHOO.util.Event.preventDefault(e); - var cs = YAHOO.util.Dom.get('diff1').value; - var url = "${h.url('files_annotate_home',repo_name=c.repo_name,revision='__CS__',f_path=c.f_path)}".replace('__CS__',cs); - window.location = url; - }); - }); - </script> - </div> - </div> -</div> -</%def>
--- a/rhodecode/templates/files/files_browser.html Mon Apr 23 18:31:51 2012 +0200 +++ b/rhodecode/templates/files/files_browser.html Thu May 10 20:27:45 2012 +0200 @@ -70,7 +70,11 @@ %for cnt,node in enumerate(c.file): <tr class="parity${cnt%2}"> <td> - ${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")} + %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():
--- a/rhodecode/templates/files/files_edit.html Mon Apr 23 18:31:51 2012 +0200 +++ b/rhodecode/templates/files/files_edit.html Thu May 10 20:27:45 2012 +0200 @@ -56,7 +56,7 @@ % 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>
--- a/rhodecode/templates/files/files_source.html Mon Apr 23 18:31:51 2012 +0200 +++ b/rhodecode/templates/files/files_source.html Thu May 10 20:27:45 2012 +0200 @@ -16,11 +16,15 @@ <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"> - ${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")} + %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): @@ -43,60 +47,66 @@ ${_('Binary file (%s)') % c.file.mimetype} %else: % if c.file.size < c.cut_off_limit: - ${h.pygmentize(c.file,linenos=True,anchorlinenos=True,lineanchors='L',cssclass="code-highlight")} + %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 - <script type="text/javascript"> - 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(){ + 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; + + } YUE.on('show_rev','click',function(e){ YUE.preventDefault(e); var cs = YUD.get('diff1').value; - var url = "${h.url('files_home',repo_name=c.repo_name,revision='__CS__',f_path=c.f_path)}".replace('__CS__',cs); + %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')}"))
--- a/rhodecode/templates/files/files_ypjax.html Mon Apr 23 18:31:51 2012 +0200 +++ b/rhodecode/templates/files/files_ypjax.html Thu May 10 20:27:45 2012 +0200 @@ -1,6 +1,9 @@ %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'/>
--- a/rhodecode/templates/forks/fork.html Mon Apr 23 18:31:51 2012 +0200 +++ b/rhodecode/templates/forks/fork.html Thu May 10 20:27:45 2012 +0200 @@ -65,7 +65,7 @@ <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">
--- a/rhodecode/templates/index_base.html Mon Apr 23 18:31:51 2012 +0200 +++ b/rhodecode/templates/index_base.html Thu May 10 20:27:45 2012 +0200 @@ -119,7 +119,7 @@ </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; }
--- a/rhodecode/templates/shortlog/shortlog_data.html Mon Apr 23 18:31:51 2012 +0200 +++ b/rhodecode/templates/shortlog/shortlog_data.html Thu May 10 20:27:45 2012 +0200 @@ -15,7 +15,7 @@ <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> @@ -25,11 +25,11 @@ <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> @@ -73,7 +73,7 @@ ${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>
--- a/rhodecode/tests/__init__.py Mon Apr 23 18:31:51 2012 +0200 +++ b/rhodecode/tests/__init__.py Thu May 10 20:27:45 2012 +0200 @@ -21,13 +21,15 @@ 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' -time.tzset() +if not is_windows: + time.tzset() log = logging.getLogger(__name__) @@ -71,6 +73,7 @@ HG_FORK = 'vcs_test_hg_fork' GIT_FORK = 'vcs_test_git_fork' + class TestController(TestCase): def __init__(self, *args, **kwargs):
--- a/rhodecode/tests/functional/test_changeset_comments.py Mon Apr 23 18:31:51 2012 +0200 +++ b/rhodecode/tests/functional/test_changeset_comments.py Thu May 10 20:27:45 2012 +0200 @@ -75,11 +75,15 @@ 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)
--- a/rhodecode/tests/functional/test_files.py Mon Apr 23 18:31:51 2012 +0200 +++ b/rhodecode/tests/functional/test_files.py Thu May 10 20:27:45 2012 +0200 @@ -129,10 +129,11 @@ 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"> @@ -196,11 +197,13 @@ repo_name=HG_REPO, fname=fname)) - assert response.status == '200 OK', 'wrong response code' - assert 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' + 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),] + ) def test_archival_wrong_ext(self): self.log_user() @@ -211,8 +214,7 @@ 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() @@ -223,7 +225,7 @@ 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 @@ -235,8 +237,8 @@ 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() @@ -276,7 +278,7 @@ 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()
--- a/rhodecode/tests/functional/test_forks.py Mon Apr 23 18:31:51 2012 +0200 +++ b/rhodecode/tests/functional/test_forks.py Thu May 10 20:27:45 2012 +0200 @@ -1,9 +1,25 @@ 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 @@ -12,7 +28,6 @@ self.assertTrue("""There are no forks yet""" in response.body) - def test_index_with_fork(self): self.log_user() @@ -34,7 +49,6 @@ 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) @@ -42,9 +56,6 @@ #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 @@ -71,11 +82,9 @@ 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 @@ -84,3 +93,41 @@ 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')
--- a/rhodecode/tests/functional/test_login.py Mon Apr 23 18:31:51 2012 +0200 +++ b/rhodecode/tests/functional/test_login.py Thu May 10 20:27:45 2012 +0200 @@ -54,7 +54,6 @@ 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', @@ -101,7 +100,7 @@ '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'), @@ -112,7 +111,7 @@ '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'), @@ -123,9 +122,8 @@ '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'), @@ -137,11 +135,11 @@ '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'), @@ -156,8 +154,6 @@ 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', @@ -170,7 +166,6 @@ 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', @@ -180,8 +175,8 @@ 'name':'test', 'lastname':'test'}) - self.assertEqual(response.status , '200 OK') - assert 'Passwords do not match' in response.body + self.assertEqual(response.status, '200 OK') + response.mustcontain('Passwords do not match') def test_register_ok(self): username = 'test_regular4' @@ -196,28 +191,32 @@ 'password_confirmation':password, 'email':email, 'name':name, - 'lastname':lastname}) - self.assertEqual(response.status , '302 Found') - assert 'You have successfully registered into rhodecode' in response.session['flash'][0], 'No flash message about user registration' + 'lastname':lastname, + 'admin':True}) # This should be overriden + self.assertEqual(response.status, '302 Found') + 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') + self.assertEqual(response.status, '200 OK') username = 'test_password_reset_1' password = 'qweqwe'
--- a/rhodecode/tests/mem_watch Mon Apr 23 18:31:51 2012 +0200 +++ b/rhodecode/tests/mem_watch Thu May 10 20:27:45 2012 +0200 @@ -1,1 +1,1 @@ -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
--- a/rhodecode/tests/rhodecode_crawler.py Mon Apr 23 18:31:51 2012 +0200 +++ b/rhodecode/tests/rhodecode_crawler.py Thu May 10 20:27:45 2012 +0200 @@ -32,30 +32,68 @@ 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') - ] + ('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() @@ -69,19 +107,21 @@ 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()) @@ -93,14 +133,11 @@ 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: @@ -124,22 +161,24 @@ 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)
--- a/rhodecode/tests/test_libs.py Mon Apr 23 18:31:51 2012 +0200 +++ b/rhodecode/tests/test_libs.py Thu May 10 20:27:45 2012 +0200 @@ -103,9 +103,16 @@ 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))
--- a/rhodecode/tests/test_models.py Mon Apr 23 18:31:51 2012 +0200 +++ b/rhodecode/tests/test_models.py Thu May 10 20:27:45 2012 +0200 @@ -5,7 +5,8 @@ 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 @@ -153,24 +154,23 @@ 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) + g2 = _make_group('t22', parent_id=g1.group_id) Session.commit() - self.assertEqual(g2.full_path,'t11/t22') + 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') + 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.assertEqual(g2.full_path, 'g22') self.assertFalse(self.__check_path('t11', 't22')) self.assertTrue(self.__check_path('g22')) @@ -620,7 +620,7 @@ # 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,