Mercurial > public > src > rhodecode
changeset 588:182ee00df287 demo
merged with default
author | Marcin Kuzminski <marcin@python-works.com> |
---|---|
date | Sun, 03 Oct 2010 02:27:44 +0200 |
parents | 0f7c14c51c5f (current diff) 168273591e44 (diff) |
children | 7622a41efb90 |
files | pylons_app/controllers/admin/repos.py pylons_app/controllers/admin/settings.py pylons_app/controllers/admin/users.py pylons_app/model/user_model.py |
diffstat | 39 files changed, 700 insertions(+), 213 deletions(-) [+] |
line wrap: on
line diff
--- a/.hgtags Tue Sep 28 20:38:35 2010 +0000 +++ b/.hgtags Sun Oct 03 02:27:44 2010 +0200 @@ -13,5 +13,4 @@ ccbe729908844884aea89d00fb14a6cb92e10c06 v0.8.2 ca41d544dbdfd2f81bd0304168492a26276aadb6 v0.8.3 2fa16ec5822da0c6fade3dd1ed9b6c0655e5dbbf v0.8.4 - - \ No newline at end of file +16ba57d8fe2317c49dbd422afd07ab497687aa02 v0.8.5
--- a/README.rst Tue Sep 28 20:38:35 2010 +0000 +++ b/README.rst Sun Oct 03 02:27:44 2010 +0200 @@ -11,7 +11,7 @@ - full permissions per project read/write/admin access even on mercurial request - mako templates let's you cusmotize look and feel of application. - diffs annotations and source code all colored by pygments. -- mercurial branch graph and yui-flot powered graphs with zooming +- mercurial branch graph and yui-flot powered graphs with zooming and statistics - admin interface for performing user/permission managments as well as repository managment. - full text search of source codes with indexing daemons using whoosh @@ -44,26 +44,38 @@ ------------- Installation ------------- -.. note:: - I recomend to install tip version of vcs while the app is in beta mode. - - -- create new virtualenv and activate it - highly recommend that you use separate - virtual-env for whole application -- download hg app from default branch from bitbucket and run - 'python setup.py install' this will install all required dependencies needed -- run paster setup-app production.ini it should create all needed tables + +- I highly recommend to install new virtualenv for hg-app see + http://pypi.python.org/pypi/virtualenv +- Create new virtualenv using `virtualenv --no-site-packages /var/www/hgapp-venv` + this will install new virtual env into /var/www/hgapp-venv. + Activate the virtualenv by running + `source activate /var/www/hgapp-venv/bin/activate` +- Make a folder for hg-app somewhere on the filesystem for example /var/www/hgapp +- Download and extract http://bitbucket.org/marcinkuzminski/hg-app/get/tip.zip + into created directory. +- Run `python setup.py install` in order to install the application and all + needed dependencies. Make sure that You're using activated virutalenv +- Run `paster setup-app production.ini` it should create all needed tables and an admin account make sure You specify correct path to repositories. -- remember that the given path for mercurial repositories must be write +- Remember that the given path for mercurial repositories must be write accessible for the application -- run paster serve development.ini - or you can use manage-hg_app script. +- Run paster serve development.ini - or you can use sample init.d scripts. the app should be available at the 127.0.0.1:5000 -- use admin account you created to login. -- default permissions on each repository is read, and owner is admin. So remember +- Use admin account you created to login. +- Default permissions on each repository is read, and owner is admin. So remember to update these. -- in order to use full power of async tasks, You must install message broker - preferrably rabbitmq and start celeryd daemon. The app should gain some speed - than. For installation instructions - You can visit: http://ask.github.com/celery/getting-started/index.html. All - needed configs are inside hg-app ie. celeryconfig.py - \ No newline at end of file +- In order to use full power of async tasks, You must install message broker + preferrably rabbitmq and start celeryd daemon together with hg-app. + The app should gain a lot of speed and become much more responsible. + For installation instructions You can visit: + http://ask.github.com/celery/getting-started/index.html. +- All needed configs are inside hg-app ie. celeryconfig.py , production.ini + You can configure the email, ports, loggers, workers from there. +- For full text search You can either put crontab entry for + `python /var/www/hgapp/pylons_app/lib/indexers/daemon.py incremental <path_to_repos>` + or run indexer from admin panel. This will scann the repos given in the + application setup or given path for daemon.py and each scann in incremental + mode will scann only changed files, + Hg Update hook must be activated to index the content it's enabled by default + after setup \ No newline at end of file
--- a/celeryconfig.py Tue Sep 28 20:38:35 2010 +0000 +++ b/celeryconfig.py Sun Oct 03 02:27:44 2010 +0200 @@ -40,6 +40,9 @@ #Tasks will never be sent to the queue, but executed locally instead. CELERY_ALWAYS_EAGER = False +if PYLONS_CONFIG_NAME == 'test.ini': + #auto eager for tests + CELERY_ALWAYS_EAGER = True #=============================================================================== # EMAIL SETTINGS
--- a/pylons_app/__init__.py Tue Sep 28 20:38:35 2010 +0000 +++ b/pylons_app/__init__.py Sun Oct 03 02:27:44 2010 +0200 @@ -24,7 +24,7 @@ @author: marcink """ -VERSION = (0, 8, 4, 'beta') +VERSION = (0, 8, 5, 'beta') __version__ = '.'.join((str(each) for each in VERSION[:4]))
--- a/pylons_app/config/routing.py Tue Sep 28 20:38:35 2010 +0000 +++ b/pylons_app/config/routing.py Sun Oct 03 02:27:44 2010 +0200 @@ -108,7 +108,8 @@ m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}', action='add_repo') #SEARCH - map.connect('search', '/_admin/search', controller='search') + map.connect('search', '/_admin/search', controller='search',) + map.connect('search_repo', '/_admin/search/{search_repo:.*}', controller='search') #LOGIN/LOGOUT/REGISTER/SIGN IN map.connect('login_home', '/_admin/login', controller='login') @@ -159,7 +160,10 @@ conditions=dict(function=check_repo)) map.connect('files_archive_home', '/{repo_name:.*}/archive/{revision}/{fileformat}', controller='files', action='archivefile', revision='tip', - conditions=dict(function=check_repo)) + conditions=dict(function=check_repo)) + map.connect('repo_settings_delete', '/{repo_name:.*}/settings', + controller='settings', action="delete", + conditions=dict(method=["DELETE"], function=check_repo)) map.connect('repo_settings_update', '/{repo_name:.*}/settings', controller='settings', action="update", conditions=dict(method=["PUT"], function=check_repo)) @@ -167,5 +171,11 @@ controller='settings', action='index', conditions=dict(function=check_repo)) - + map.connect('repo_fork_create_home', '/{repo_name:.*}/fork', + controller='settings', action='fork_create', + conditions=dict(function=check_repo, method=["POST"])) + map.connect('repo_fork_home', '/{repo_name:.*}/fork', + controller='settings', action='fork', + conditions=dict(function=check_repo)) + return map
--- a/pylons_app/controllers/admin/repos.py Tue Sep 28 20:38:35 2010 +0000 +++ b/pylons_app/controllers/admin/repos.py Sun Oct 03 02:27:44 2010 +0200 @@ -32,7 +32,7 @@ from pylons_app.lib.auth import LoginRequired, HasPermissionAllDecorator, \ HasPermissionAnyDecorator from pylons_app.lib.base import BaseController, render -from pylons_app.lib.utils import invalidate_cache +from pylons_app.lib.utils import invalidate_cache, action_logger from pylons_app.model.db import User from pylons_app.model.forms import RepoForm from pylons_app.model.hg_model import HgModel @@ -77,13 +77,20 @@ invalidate_cache('cached_repo_list') h.flash(_('created repository %s') % form_result['repo_name'], category='success') - + + if request.POST.get('user_created'): + action_logger(self.hg_app_user, 'user_created_repo', + form_result['repo_name'], '', self.sa) + else: + action_logger(self.hg_app_user, 'admin_created_repo', + form_result['repo_name'], '', self.sa) + except formencode.Invalid as errors: c.new_repo = errors.value['repo_name'] if request.POST.get('user_created'): r = render('admin/repos/repo_add_create_repository.html') - else: + else: r = render('admin/repos/repo_add.html') return htmlfill.render( @@ -173,10 +180,14 @@ return redirect(url('repos')) try: + action_logger(self.hg_app_user, 'admin_deleted_repo', + repo_name, '', self.sa) repo_model.delete(repo) invalidate_cache('cached_repo_list') h.flash(_('deleted repository %s') % repo_name, category='success') - except Exception: + + except Exception, e: + log.error(traceback.format_exc()) h.flash(_('An error occured during deletion of %s') % repo_name, category='error')
--- a/pylons_app/controllers/admin/settings.py Tue Sep 28 20:38:35 2010 +0000 +++ b/pylons_app/controllers/admin/settings.py Sun Oct 03 02:27:44 2010 +0200 @@ -101,7 +101,7 @@ initial = HgModel.repo_scan(g.paths[0][0], g.paths[0][1], g.baseui) repo2db_mapper(initial, rm_obsolete) invalidate_cache('cached_repo_list') - h.flash(_('Repositories sucessfully rescanned'), category='success') + h.flash(_('Repositories successfully rescanned'), category='success') if setting_id == 'whoosh': repo_location = get_hg_ui_settings()['paths_root_path'] @@ -134,7 +134,7 @@ except: log.error(traceback.format_exc()) - h.flash(_('error occured during updating application settings'), + h.flash(_('error occurred during updating application settings'), category='error') self.sa.rollback() @@ -187,7 +187,7 @@ except: log.error(traceback.format_exc()) - h.flash(_('error occured during updating application settings'), + h.flash(_('error occurred during updating application settings'), category='error') self.sa.rollback()
--- a/pylons_app/controllers/admin/users.py Tue Sep 28 20:38:35 2010 +0000 +++ b/pylons_app/controllers/admin/users.py Sun Oct 03 02:27:44 2010 +0200 @@ -17,6 +17,7 @@ # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, # MA 02110-1301, USA. +from pylons_app.lib.utils import action_logger """ Created on April 4, 2010 users controller for pylons @@ -71,6 +72,7 @@ user_model.create(form_result) h.flash(_('created user %s') % form_result['username'], category='success') + #action_logger(self.hg_app_user, 'new_user', '', '', self.sa) except formencode.Invalid as errors: return htmlfill.render( render('admin/users/user_add.html'),
--- a/pylons_app/controllers/search.py Tue Sep 28 20:38:35 2010 +0000 +++ b/pylons_app/controllers/search.py Sun Oct 03 02:27:44 2010 +0200 @@ -44,7 +44,8 @@ def __before__(self): super(SearchController, self).__before__() - def index(self): + def index(self, search_repo=None): + c.repo_name = search_repo c.formated_results = [] c.runtime = '' c.cur_query = request.GET.get('q', None) @@ -59,6 +60,8 @@ searcher = idx.searcher() qp = QueryParser("content", schema=SCHEMA) + if c.repo_name: + cur_query = u'repository:%s %s' % (c.repo_name, cur_query) try: query = qp.parse(unicode(cur_query))
--- a/pylons_app/controllers/settings.py Tue Sep 28 20:38:35 2010 +0000 +++ b/pylons_app/controllers/settings.py Sun Oct 03 02:27:44 2010 +0200 @@ -28,8 +28,8 @@ from pylons.i18n.translation import _ from pylons_app.lib.auth import LoginRequired, HasRepoPermissionAllDecorator from pylons_app.lib.base import BaseController, render -from pylons_app.lib.utils import invalidate_cache -from pylons_app.model.forms import RepoSettingsForm +from pylons_app.lib.utils import invalidate_cache, action_logger +from pylons_app.model.forms import RepoSettingsForm, RepoForkForm from pylons_app.model.repo_model import RepoModel import formencode import logging @@ -55,7 +55,7 @@ ' in order to rescan repositories') % repo_name, category='error') - return redirect(url('repos')) + return redirect(url('hg_home')) defaults = c.repo_info.__dict__ defaults.update({'user':c.repo_info.user.username}) c.users_array = repo_model.get_users_js() @@ -79,7 +79,7 @@ form_result = _form.to_python(dict(request.POST)) repo_model.update(repo_name, form_result) invalidate_cache('cached_repo_list') - h.flash(_('Repository %s updated succesfully' % repo_name), + h.flash(_('Repository %s updated successfully' % repo_name), category='success') changed_name = form_result['repo_name'] except formencode.Invalid as errors: @@ -98,3 +98,78 @@ % repo_name, category='error') return redirect(url('repo_settings_home', repo_name=changed_name)) + + + + def delete(self, repo_name): + """DELETE /repos/repo_name: Delete an existing item""" + # Forms posted to this method should contain a hidden field: + # <input type="hidden" name="_method" value="DELETE" /> + # Or using helpers: + # h.form(url('repo_settings_delete', repo_name=ID), + # method='delete') + # url('repo_settings_delete', repo_name=ID) + + repo_model = RepoModel() + repo = repo_model.get(repo_name) + if not repo: + h.flash(_('%s repository is not mapped to db perhaps' + ' it was moved or renamed from the filesystem' + ' please run the application again' + ' in order to rescan repositories') % repo_name, + category='error') + + return redirect(url('hg_home')) + try: + action_logger(self.hg_app_user, 'user_deleted_repo', + repo_name, '', self.sa) + repo_model.delete(repo) + invalidate_cache('cached_repo_list') + h.flash(_('deleted repository %s') % repo_name, category='success') + except Exception: + h.flash(_('An error occurred during deletion of %s') % repo_name, + category='error') + + return redirect(url('hg_home')) + + def fork(self, repo_name): + repo_model = RepoModel() + c.repo_info = repo = repo_model.get(repo_name) + if not repo: + h.flash(_('%s repository is not mapped to db perhaps' + ' it was created or renamed from the filesystem' + ' please run the application again' + ' in order to rescan repositories') % repo_name, + category='error') + + return redirect(url('hg_home')) + + return render('settings/repo_fork.html') + + + + def fork_create(self, repo_name): + repo_model = RepoModel() + c.repo_info = repo_model.get(repo_name) + _form = RepoForkForm()() + form_result = {} + try: + form_result = _form.to_python(dict(request.POST)) + form_result.update({'repo_name':repo_name}) + repo_model.create_fork(form_result, c.hg_app_user) + h.flash(_('fork %s repository as %s task added') \ + % (repo_name, form_result['fork_name']), + category='success') + action_logger(self.hg_app_user, 'user_forked_repo', + repo_name, '', self.sa) + except formencode.Invalid as errors: + c.new_repo = errors.value['fork_name'] + r = render('settings/repo_fork.html') + + return htmlfill.render( + r, + defaults=errors.value, + errors=errors.error_dict or {}, + prefix_error=False, + encoding="UTF-8") + return redirect(url('hg_home'))
--- a/pylons_app/lib/base.py Tue Sep 28 20:38:35 2010 +0000 +++ b/pylons_app/lib/base.py Sun Oct 03 02:27:44 2010 +0200 @@ -22,8 +22,14 @@ c.repo_switcher_list = _get_repos_switcher_cached(c.cached_repo_list) if c.repo_name: - c.repository_tags = c.cached_repo_list[c.repo_name].tags - c.repository_branches = c.cached_repo_list[c.repo_name].branches + cached_repo = c.cached_repo_list.get(c.repo_name) + + if cached_repo: + c.repository_tags = cached_repo.tags + c.repository_branches = cached_repo.branches + else: + c.repository_tags = {} + c.repository_branches = {} self.sa = meta.Session @@ -34,7 +40,7 @@ # available in environ['pylons.routes_dict'] try: #putting this here makes sure that we update permissions every time - c.hg_app_user = auth.get_user(session) + self.hg_app_user = c.hg_app_user = auth.get_user(session) return WSGIController.__call__(self, environ, start_response) finally: meta.Session.remove()
--- a/pylons_app/lib/celerylib/tasks.py Tue Sep 28 20:38:35 2010 +0000 +++ b/pylons_app/lib/celerylib/tasks.py Sun Oct 03 02:27:44 2010 +0200 @@ -271,6 +271,22 @@ return False return True +@task +def create_repo_fork(form_data, cur_user): + import os + from pylons_app.model.repo_model import RepoModel + sa = get_session() + rm = RepoModel(sa) + + rm.create(form_data, cur_user, just_db=True, fork=True) + + repos_path = get_hg_ui_settings()['paths_root_path'].replace('*', '') + repo_path = os.path.join(repos_path, form_data['repo_name']) + repo_fork_path = os.path.join(repos_path, form_data['fork_name']) + + MercurialRepository(str(repo_fork_path), True, clone_url=str(repo_path)) + + def __get_codes_stats(repo_name): LANGUAGES_EXTENSIONS = ['action', 'adp', 'ashx', 'asmx', 'aspx', 'asx', 'axd', 'c', 'cfg', 'cfm', 'cpp', 'cs', 'diff', 'do', 'el', 'erl',
--- a/pylons_app/lib/db_manage.py Tue Sep 28 20:38:35 2010 +0000 +++ b/pylons_app/lib/db_manage.py Sun Oct 03 02:27:44 2010 +0200 @@ -88,9 +88,9 @@ self.create_user(username, password, email, True) else: log.info('creating admin and regular test users') - self.create_user('test_admin', 'test', 'test_admin@mail.com', True) - self.create_user('test_regular', 'test', 'test_regular@mail.com', False) - self.create_user('test_regular2', 'test', 'test_regular2@mail.com', False) + self.create_user('test_admin', 'test12', 'test_admin@mail.com', True) + self.create_user('test_regular', 'test12', 'test_regular@mail.com', False) + self.create_user('test_regular2', 'test12', 'test_regular2@mail.com', False)
--- a/pylons_app/lib/hooks.py Tue Sep 28 20:38:35 2010 +0000 +++ b/pylons_app/lib/hooks.py Sun Oct 03 02:27:44 2010 +0200 @@ -63,10 +63,10 @@ username = kwargs['url'].split(':')[-1] user_log = sa.query(UserLog)\ .filter(UserLog.user == sa.query(User)\ - .filter(User.username == username).one())\ + .filter(User.username == username).one())\ .order_by(UserLog.user_log_id.desc()).first() - - if not user_log.revision: + + if user_log and not user_log.revision: user_log.revision = str(repo['tip']) sa.add(user_log) sa.commit()
--- a/pylons_app/lib/middleware/simplehg.py Tue Sep 28 20:38:35 2010 +0000 +++ b/pylons_app/lib/middleware/simplehg.py Sun Oct 03 02:27:44 2010 +0200 @@ -24,7 +24,6 @@ SimpleHG middleware for handling mercurial protocol request (push/clone etc.) It's implemented with basic auth function """ -from datetime import datetime from itertools import chain from mercurial.error import RepoError from mercurial.hgweb import hgweb @@ -35,12 +34,10 @@ get_user_cached from pylons_app.lib.utils import is_mercurial, make_ui, invalidate_cache, \ check_repo_fast, ui_sections -from pylons_app.model import meta -from pylons_app.model.db import UserLog, User from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError +from pylons_app.lib.utils import action_logger import logging import os -import pylons_app.lib.helpers as h import traceback log = logging.getLogger(__name__) @@ -183,23 +180,7 @@ return mapping[cmd] def __log_user_action(self, user, action, repo, ipaddr): - sa = meta.Session - try: - user_log = UserLog() - user_log.user_id = user.user_id - user_log.action = action - user_log.repository = repo.replace('/', '') - user_log.action_date = datetime.now() - user_log.user_ip = ipaddr - sa.add(user_log) - sa.commit() - log.info('Adding user %s, action %s on %s', - user.username, action, repo) - except Exception, e: - sa.rollback() - log.error('could not log user action:%s', str(e)) - finally: - meta.Session.remove() + action_logger(user, action, repo, ipaddr) def __invalidate_cache(self, repo_name): """we know that some change was made to repositories and we should
--- a/pylons_app/lib/utils.py Tue Sep 28 20:38:35 2010 +0000 +++ b/pylons_app/lib/utils.py Sun Oct 03 02:27:44 2010 +0200 @@ -26,16 +26,17 @@ from mercurial import ui, config, hg from mercurial.error import RepoError from pylons_app.model import meta -from pylons_app.model.db import Repository, User, HgAppUi, HgAppSettings +from pylons_app.model.db import Repository, User, HgAppUi, HgAppSettings, UserLog from vcs.backends.base import BaseChangeset from vcs.utils.lazy import LazyProperty import logging +import datetime import os log = logging.getLogger(__name__) -def get_repo_slug(request): +def get_repo_slug(request): return request.environ['pylons.routes_dict'].get('repo_name') def is_mercurial(environ): @@ -48,6 +49,40 @@ return True return False +def action_logger(user, action, repo, ipaddr, sa=None): + """ + Action logger for various action made by users + """ + + if not sa: + sa = meta.Session + + try: + if hasattr(user, 'user_id'): + user_id = user.user_id + elif isinstance(user, basestring): + user_id = sa.query(User).filter(User.username == user).one() + else: + raise Exception('You have to provide user object or username') + + repo_name = repo.lstrip('/') + user_log = UserLog() + user_log.user_id = user_id + user_log.action = action + user_log.repository_name = repo_name + user_log.repository = sa.query(Repository)\ + .filter(Repository.repo_name == repo_name).one() + user_log.action_date = datetime.datetime.now() + user_log.user_ip = ipaddr + sa.add(user_log) + sa.commit() + log.info('Adding user %s, action %s on %s', + user.username, action, repo) + except Exception, e: + raise + sa.rollback() + log.error('could not log user action:%s', str(e)) + def check_repo_dir(paths): repos_path = paths[0][1].split('/') if repos_path[-1] in ['*', '**']: @@ -227,8 +262,11 @@ Returns raw string identifing this changeset, useful for web representation. """ - return '0' * 12 - + return '0' * 40 + + @LazyProperty + def short_id(self): + return self.raw_id[:12] def repo2db_mapper(initial_repo_list, remove_obsolete=False): """
--- a/pylons_app/model/db.py Tue Sep 28 20:38:35 2010 +0000 +++ b/pylons_app/model/db.py Sun Oct 03 02:27:44 2010 +0200 @@ -66,12 +66,15 @@ __table_args__ = {'useexisting':True} user_log_id = Column("user_log_id", INTEGER(), nullable=False, unique=True, default=None, primary_key=True) user_id = Column("user_id", INTEGER(), ForeignKey(u'users.user_id'), nullable=False, unique=None, default=None) + repository_id = Column("repository_id", INTEGER(length=None, convert_unicode=False, assert_unicode=None), ForeignKey(u'repositories.repo_id'), nullable=False, unique=None, default=None) + repository_name = Column("repository_name", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) user_ip = Column("user_ip", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) - repository = Column("repository", TEXT(length=None, convert_unicode=False, assert_unicode=None), ForeignKey(u'repositories.repo_name'), nullable=False, unique=None, default=None) action = Column("action", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) action_date = Column("action_date", DATETIME(timezone=False), nullable=True, unique=None, default=None) revision = Column('revision', TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + user = relation('User') + repository = relation('Repository') class Repository(Base): __tablename__ = 'repositories' @@ -81,8 +84,10 @@ user_id = Column("user_id", INTEGER(), ForeignKey(u'users.user_id'), nullable=False, unique=False, default=None) private = Column("private", BOOLEAN(), nullable=True, unique=None, default=None) description = Column("description", TEXT(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None) + fork_id = Column("fork_id", INTEGER(), ForeignKey(u'repositories.repo_id'), nullable=True, unique=False, default=None) user = relation('User') + fork = relation('Repository', remote_side=repo_id) repo_to_perm = relation('RepoToPerm', cascade='all') def __repr__(self):
--- a/pylons_app/model/forms.py Tue Sep 28 20:38:35 2010 +0000 +++ b/pylons_app/model/forms.py Sun Oct 03 02:27:44 2010 +0200 @@ -247,7 +247,7 @@ filter_extra_fields = True username = UnicodeString( strip=True, - min=3, + min=1, not_empty=True, messages={ 'empty':_('Please enter a login'), @@ -256,7 +256,7 @@ password = UnicodeString( strip=True, - min=3, + min=6, not_empty=True, messages={ 'empty':_('Please enter a password'), @@ -271,15 +271,15 @@ class _UserForm(formencode.Schema): allow_extra_fields = True filter_extra_fields = True - username = All(UnicodeString(strip=True, min=3, not_empty=True), ValidUsername(edit, old_data)) + username = All(UnicodeString(strip=True, min=1, not_empty=True), ValidUsername(edit, old_data)) if edit: - new_password = All(UnicodeString(strip=True, min=3, not_empty=False), ValidPassword) + new_password = All(UnicodeString(strip=True, min=6, not_empty=False), ValidPassword) admin = StringBoolean(if_missing=False) else: - password = All(UnicodeString(strip=True, min=8, not_empty=True), ValidPassword) + password = All(UnicodeString(strip=True, min=6, not_empty=True), ValidPassword) active = StringBoolean(if_missing=False) - name = UnicodeString(strip=True, min=3, not_empty=True) - lastname = UnicodeString(strip=True, min=3, not_empty=True) + name = UnicodeString(strip=True, min=1, not_empty=True) + lastname = UnicodeString(strip=True, min=1, not_empty=True) email = All(Email(not_empty=True), UniqSystemEmail(old_data)) return _UserForm @@ -298,7 +298,7 @@ allow_extra_fields = True filter_extra_fields = False repo_name = All(UnicodeString(strip=True, min=1, not_empty=True), ValidRepoName(edit, old_data)) - description = UnicodeString(strip=True, min=3, not_empty=True) + description = UnicodeString(strip=True, min=1, not_empty=True) private = StringBoolean(if_missing=False) if edit: @@ -307,12 +307,22 @@ chained_validators = [ValidPerms] return _RepoForm +def RepoForkForm(edit=False, old_data={}): + class _RepoForkForm(formencode.Schema): + allow_extra_fields = True + filter_extra_fields = False + fork_name = All(UnicodeString(strip=True, min=1, not_empty=True), ValidRepoName(edit, old_data)) + description = UnicodeString(strip=True, min=1, not_empty=True) + private = StringBoolean(if_missing=False) + + return _RepoForkForm + def RepoSettingsForm(edit=False, old_data={}): class _RepoForm(formencode.Schema): allow_extra_fields = True filter_extra_fields = False repo_name = All(UnicodeString(strip=True, min=1, not_empty=True), ValidRepoName(edit, old_data)) - description = UnicodeString(strip=True, min=3, not_empty=True) + description = UnicodeString(strip=True, min=1, not_empty=True) private = StringBoolean(if_missing=False) chained_validators = [ValidPerms, ValidSettings] @@ -323,8 +333,8 @@ class _ApplicationSettingsForm(formencode.Schema): allow_extra_fields = True filter_extra_fields = False - hg_app_title = UnicodeString(strip=True, min=3, not_empty=True) - hg_app_realm = UnicodeString(strip=True, min=3, not_empty=True) + hg_app_title = UnicodeString(strip=True, min=1, not_empty=True) + hg_app_realm = UnicodeString(strip=True, min=1, not_empty=True) return _ApplicationSettingsForm @@ -333,7 +343,7 @@ allow_extra_fields = True filter_extra_fields = False web_push_ssl = OneOf(['true', 'false'], if_missing='false') - paths_root_path = All(ValidPath(), UnicodeString(strip=True, min=3, not_empty=True)) + paths_root_path = All(ValidPath(), UnicodeString(strip=True, min=1, not_empty=True)) hooks_changegroup_update = OneOf(['True', 'False'], if_missing=False) hooks_changegroup_repo_size = OneOf(['True', 'False'], if_missing=False)
--- a/pylons_app/model/hg_model.py Tue Sep 28 20:38:35 2010 +0000 +++ b/pylons_app/model/hg_model.py Sun Oct 03 02:27:44 2010 +0200 @@ -26,10 +26,12 @@ from mercurial import ui from mercurial.hgweb.hgwebdir_mod import findrepos from pylons.i18n.translation import _ +from pylons_app.lib import helpers as h +from pylons_app.lib.utils import invalidate_cache from pylons_app.lib.auth import HasRepoPermissionAny from pylons_app.model import meta from pylons_app.model.db import Repository, User -from pylons_app.lib import helpers as h +from sqlalchemy.orm import joinedload from vcs.exceptions import RepositoryError, VCSError import logging import os @@ -122,8 +124,12 @@ dbrepo = None if not initial: + #for initial scann on application first run we don't + #have db repos yet. dbrepo = sa.query(Repository)\ - .filter(Repository.repo_name == name).scalar() + .options(joinedload(Repository.fork))\ + .filter(Repository.repo_name == name)\ + .scalar() if dbrepo: log.info('Adding db instance to cached list') @@ -166,4 +172,15 @@ yield tmp_d def get_repo(self, repo_name): - return _get_repos_cached()[repo_name] + try: + repo = _get_repos_cached()[repo_name] + return repo + except KeyError: + #i we're here and we got key errors let's try to invalidate the + #cahce and try again + invalidate_cache('cached_repo_list') + repo = _get_repos_cached()[repo_name] + return repo + + +
--- a/pylons_app/model/repo_model.py Tue Sep 28 20:38:35 2010 +0000 +++ b/pylons_app/model/repo_model.py Sun Oct 03 02:27:44 2010 +0200 @@ -27,6 +27,7 @@ from pylons_app.model.db import Repository, RepoToPerm, User, Permission from pylons_app.model.meta import Session from pylons_app.model.user_model import UserModel +from pylons_app.lib.celerylib.tasks import create_repo_fork, run_task import logging import os import shutil @@ -35,11 +36,15 @@ class RepoModel(object): - def __init__(self): - self.sa = Session() + def __init__(self, sa=None): + if not sa: + self.sa = Session() + else: + self.sa = sa def get(self, id): - return self.sa.query(Repository).filter(Repository.repo_name == id).scalar() + return self.sa.query(Repository)\ + .filter(Repository.repo_name == id).scalar() def get_users_js(self): @@ -100,20 +105,32 @@ self.sa.rollback() raise - def create(self, form_data, cur_user, just_db=False): + def create(self, form_data, cur_user, just_db=False, fork=False): try: - repo_name = form_data['repo_name'] + if fork: + repo_name = str(form_data['fork_name']) + org_name = str(form_data['repo_name']) + + else: + org_name = repo_name = str(form_data['repo_name']) new_repo = Repository() for k, v in form_data.items(): + if k == 'repo_name': + v = repo_name setattr(new_repo, k, v) + if fork: + parent_repo = self.sa.query(Repository)\ + .filter(Repository.repo_name == org_name).scalar() + new_repo.fork = parent_repo + new_repo.user_id = cur_user.user_id self.sa.add(new_repo) #create default permission repo_to_perm = RepoToPerm() default = 'repository.read' - for p in UserModel().get_default().user_perms: + for p in UserModel(self.sa).get_default().user_perms: if p.permission.permission_name.startswith('repository.'): default = p.permission.permission_name break @@ -136,7 +153,10 @@ log.error(traceback.format_exc()) self.sa.rollback() raise - + + def create_fork(self, form_data, cur_user): + run_task(create_repo_fork, form_data, cur_user) + def delete(self, repo): try: self.sa.delete(repo)
--- a/pylons_app/model/user_model.py Tue Sep 28 20:38:35 2010 +0000 +++ b/pylons_app/model/user_model.py Sun Oct 03 02:27:44 2010 +0200 @@ -36,8 +36,11 @@ class UserModel(object): - def __init__(self): - self.sa = Session() + def __init__(self, sa=None): + if not sa: + self.sa = Session() + else: + self.sa = sa def get_default(self): return self.sa.query(User).filter(User.username == 'default').scalar()
--- a/pylons_app/public/css/style.css Tue Sep 28 20:38:35 2010 +0000 +++ b/pylons_app/public/css/style.css Sun Oct 03 02:27:44 2010 +0200 @@ -14,7 +14,7 @@ height: 100%; background: #d1d1d1 url("../images/background.png") repeat; font-family: Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif; - font-size: 11px; + font-size: 12px; } /* ----------------------------------------------------------- @@ -569,18 +569,47 @@ width:167px; } -#header #header-inner #quick li ul li a.branches,#header #header-inner #quick li ul li a.branches:hover -{ - + +#header #header-inner #quick li ul li a.fork, +#header #header-inner #quick li ul li a.fork:hover +{ + + background: #FFFFFF url("../images/icons/arrow_divide.png") no-repeat 4px 9px; + margin:0; + padding:12px 9px 7px 24px; + width:167px; +} + +#header #header-inner #quick li ul li a.search, +#header #header-inner #quick li ul li a.search:hover +{ + background: #FFFFFF url("../images/icons/search_16.png") no-repeat 4px 9px; + margin:0; + padding:12px 9px 7px 24px; + width:167px; +} + +#header #header-inner #quick li ul li a.delete, +#header #header-inner #quick li ul li a.delete:hover +{ + background: #FFFFFF url("../images/icons/delete.png") no-repeat 4px 9px; + margin:0; + padding:12px 9px 7px 24px; + width:167px; +} + +#header #header-inner #quick li ul li a.branches, +#header #header-inner #quick li ul li a.branches:hover +{ background: #FFFFFF url("../images/icons/arrow_branch.png") no-repeat 4px 9px; margin:0; padding:12px 9px 7px 24px; width:167px; } -#header #header-inner #quick li ul li a.tags,#header #header-inner #quick li ul li a.tags:hover -{ - +#header #header-inner #quick li ul li a.tags, +#header #header-inner #quick li ul li a.tags:hover +{ background: #FFFFFF url("../images/icons/tag_blue.png") no-repeat 4px 9px; margin:0; padding:12px 9px 7px 24px; @@ -1132,7 +1161,7 @@ #content div.box div.message { margin: 0 0 0px 0; - padding: 0 0 10px 0; + padding: 10px 0 10px 0; clear: both; overflow: hidden; } @@ -3029,7 +3058,9 @@ } #changeset_content .container { - height: 120px; + min-height: 120px; + font-size: 1.2em; + } #changeset_content .container .left { @@ -3123,7 +3154,7 @@ border-bottom: 1px solid #CCCCCC; border-left: 1px solid #CCCCCC; border-right: 1px solid #CCCCCC; - min-height: 90px; + min-height: 80px; overflow: hidden; font-size:1.2em; } @@ -3161,7 +3192,6 @@ border: 1px solid #DDDDDD; display: block; float: right; - font-size: 0.75em; text-align: center; min-width: 15px; } @@ -3193,7 +3223,16 @@ font-family: monospace; } - +.right .logtags .branchtag{ + background: #FFFFFF url("../images/icons/arrow_branch.png") no-repeat right 6px; + display:block; + padding:8px 16px 0px 0px +} +.right .logtags .tagtag{ + background: #FFFFFF url("../images/icons/tag_blue.png") no-repeat right 6px; + display:block; + padding:6px 18px 0px 0px +} /* ----------------------------------------------------------- FILE BROWSER @@ -3464,12 +3503,14 @@ .action_button { border: 0px; display: block; + color:#0066CC; } .action_button:hover { border: 0px; - font-style: italic; + text-decoration:underline; cursor: pointer; + color:#0066CC; } /* -----------------------------------------------------------
--- a/pylons_app/templates/admin/admin_log.html Tue Sep 28 20:38:35 2010 +0000 +++ b/pylons_app/templates/admin/admin_log.html Sun Oct 03 02:27:44 2010 +0200 @@ -12,11 +12,17 @@ %for cnt,l in enumerate(c.users_log): <tr class="parity${cnt%2}"> <td>${h.link_to(l.user.username,h.url('edit_user', id=l.user.user_id))}</td> - <td>${h.link_to(l.repository,h.url('summary_home',repo_name=l.repository))}</td> + <td> + %if l.repository: + ${h.link_to(l.repository.repo_name,h.url('summary_home',repo_name=l.repository.repo_name))} + %else: + ${l.repository_name} + %endif + </td> <td> % if l.action == 'push' and l.revision: ${h.link_to('%s - %s' % (l.action,l.revision), - h.url('changeset_home',repo_name=l.repository,revision=l.revision))} + h.url('changeset_home',repo_name=l.repository.repo_name,revision=l.revision))} %else: ${l.action} %endif
--- a/pylons_app/templates/admin/repos/repo_add.html Tue Sep 28 20:38:35 2010 +0000 +++ b/pylons_app/templates/admin/repos/repo_add.html Sun Oct 03 02:27:44 2010 +0200 @@ -2,7 +2,7 @@ <%inherit file="/base/base.html"/> <%def name="title()"> - ${_('Repositories administration')} + ${_('Add new repository')} </%def> <%def name="breadcrumbs_links()">
--- a/pylons_app/templates/admin/repos/repos.html Tue Sep 28 20:38:35 2010 +0000 +++ b/pylons_app/templates/admin/repos/repos.html Sun Oct 03 02:27:44 2010 +0200 @@ -27,9 +27,11 @@ <div class="table"> <table class="table_disp"> <tr class="header"> - <th class="left">${_('name')}</th> - <th class="left">${_('owner')}</th> - <th class="left">${_('last revision')}</th> + <th class="left">${_('Name')}</th> + <th class="left">${_('Description')}</th> + <th class="left">${_('Last change')}</th> + <th class="left">${_('Tip')}</th> + <th class="left">${_('Contact')}</th> <th class="left">${_('action')}</th> </tr> %for cnt,repo in enumerate(c.repos_list): @@ -40,9 +42,28 @@ %else: <img alt="${_('public')}" src="/images/icons/lock_open.png"/> %endif - ${h.link_to(repo['name'],h.url('edit_repo',repo_name=repo['name']))}</td> - <td>${repo['contact']}</td> - <td>r${repo['rev']}:${repo['tip']}</td> + ${h.link_to(repo['name'],h.url('edit_repo',repo_name=repo['name']))} + + %if repo['repo'].dbrepo.fork: + <a href="${h.url('summary_home',repo_name=repo['repo'].dbrepo.fork.repo_name)}"> + <img class="icon" alt="${_('public')}" + title="${_('Fork of')} ${repo['repo'].dbrepo.fork.repo_name}" + src="/images/icons/arrow_divide.png"/></a> + %endif + </td> + <td title="${repo['description']}">${h.truncate(repo['description'],60)}</td> + <td>${h.age(repo['last_change'])}</td> + <td> + %if repo['rev']>=0: + ${h.link_to('r%s:%s' % (repo['rev'],repo['tip']), + h.url('changeset_home',repo_name=repo['name'],revision=repo['tip']), + class_="tooltip", + tooltip_title=h.tooltip(repo['last_msg']))} + %else: + ${_('No changesets yet')} + %endif + </td> + <td title="${repo['contact']}">${h.person(repo['contact'])}</td> <td> ${h.form(url('repo', repo_name=repo['name']),method='delete')} ${h.submit('remove_%s' % repo['name'],'delete',class_="delete_icon action_button",onclick="return confirm('Confirm to delete this repository');")}
--- a/pylons_app/templates/admin/users/user_edit_my_account.html Tue Sep 28 20:38:35 2010 +0000 +++ b/pylons_app/templates/admin/users/user_edit_my_account.html Sun Oct 03 02:27:44 2010 +0200 @@ -88,21 +88,40 @@ <div class="table"> <table> <tbody> - %for repo in c.user_repos: - <tr> - <td> - %if repo.dbrepo.private: - <img class="icon" alt="${_('private')}" src="/images/icons/lock.png"/> - %else: - <img class="icon" alt="${_('public')}" src="/images/icons/lock_open.png"/> - %endif - - ${h.link_to(repo.name, h.url('summary_home',repo_name=repo.name))}</td> - <td>${_('revision')}: ${h.get_changeset_safe(repo,'tip').revision}</td> - <td>${_('last changed')}: ${h.age(repo.last_change)}</td> - <td><img class="icon" alt="${_('private')}" src="/images/icons/application_form_edit.png"/> ${h.link_to(_('edit'),h.url('edit_repo',repo_name=repo.name))}</td> - </tr> - %endfor + %if c.user_repos: + %for repo in c.user_repos: + <tr> + <td> + %if repo.dbrepo.private: + <img class="icon" alt="${_('private')}" src="/images/icons/lock.png"/> + %else: + <img class="icon" alt="${_('public')}" src="/images/icons/lock_open.png"/> + %endif + + ${h.link_to(repo.name, h.url('summary_home',repo_name=repo.name))} + %if repo.dbrepo.fork: + <a href="${h.url('summary_home',repo_name=repo.dbrepo.fork.repo_name)}"> + <img class="icon" alt="${_('public')}" + title="${_('Fork of')} ${repo.dbrepo.fork.repo_name}" + src="/images/icons/arrow_divide.png"/></a> + %endif + </td> + <td>${_('revision')}: ${h.get_changeset_safe(repo,'tip').revision}</td> + <td>${_('last changed')}: ${h.age(repo.last_change)}</td> + <td><img class="icon" alt="${_('private')}" src="/images/icons/application_form_edit.png"/> ${h.link_to(_('edit'),h.url('repo_settings_home',repo_name=repo.name))}</td> + <td> + ${h.form(url('repo_settings_delete', repo_name=repo.name),method='delete')} + ${h.submit('remove_%s' % repo.name,'delete',class_="delete_icon action_button",onclick="return confirm('Confirm to delete this repository');")} + ${h.end_form()} + </td> + </tr> + %endfor + %else: + ${_('No repositories yet')} + %if h.HasPermissionAny('hg.admin','hg.create.repository')(): + ${h.link_to(_('create one now'),h.url('admin_settings_create_repository'))} + %endif + %endif </tbody> </table> </div>
--- a/pylons_app/templates/base/base.html Tue Sep 28 20:38:35 2010 +0000 +++ b/pylons_app/templates/base/base.html Sun Oct 03 02:27:44 2010 +0200 @@ -174,16 +174,30 @@ <span>${_('Files')}</span> </a> </li> - %if h.HasRepoPermissionAll('repository.admin')(c.repo_name): - <li ${is_current('settings')}> - <a title="${_('Settings')}" href="${h.url('repo_settings_home',repo_name=c.repo_name)}"> + + <li ${is_current('options')}> + <a title="${_('Options')}" href="#"> <span class="icon"> - <img src="/images/icons/cog_edit.png" alt="${_('Settings')}" /> + <img src="/images/icons/table_gear.png" alt="${_('Admin')}" /> </span> - <span>${_('Settings')}</span> - </a> - </li> - %endif + <span>${_('Options')}</span> + </a> + <ul> + %if h.HasRepoPermissionAll('repository.admin')(c.repo_name): + <li>${h.link_to(_('settings'),h.url('repo_settings_home',repo_name=c.repo_name),class_='settings')}</li> + %endif + <li>${h.link_to(_('fork'),h.url('repo_fork_home',repo_name=c.repo_name),class_='fork')}</li> + <li>${h.link_to(_('search'),h.url('search_repo',search_repo=c.repo_name),class_='search')}</li> +## %if h.HasRepoPermissionAll('repository.admin')(c.repo_name): +## <li class="last"> +## ${h.link_to(_('delete'),'#',class_='delete')} +## ${h.form(url('repo_settings_delete', repo_name=c.repo_name),method='delete')} +## ${h.submit('remove_%s' % c.repo_name,'delete',class_="delete_icon action_button",onclick="return confirm('Confirm to delete this repository');")} +## ${h.end_form()} +## </li> +## %endif + </ul> + </li> </ul> %else: ##ROOT MENU
--- a/pylons_app/templates/changelog/changelog.html Tue Sep 28 20:38:35 2010 +0000 +++ b/pylons_app/templates/changelog/changelog.html Sun Oct 03 02:27:44 2010 +0200 @@ -3,7 +3,7 @@ <%inherit file="/base/base.html"/> <%def name="title()"> - ${_('Changelog - %s') % c.repo_name} + ${_('Changelog')} - ${c.repo_name} </%def> <%def name="breadcrumbs_links()"> @@ -47,12 +47,6 @@ <div id="chg_${cnt+1}" class="container"> <div class="left"> <div class="date">${_('commit')} ${cs.revision}: ${cs.short_id}@${cs.date}</div> - <span class="logtags"> - <span class="branchtag">${cs.branch}</span> - %for tag in cs.tags: - <span class="tagtag">${tag}</span> - %endfor - </span> <div class="author"> <div class="gravatar"> <img alt="gravatar" src="${h.gravatar_url(h.email(cs.author),20)}"/> @@ -80,7 +74,15 @@ <div class="parent">${_('Parent')} ${p_cs.revision}: ${h.link_to(p_cs.short_id, h.url('changeset_home',repo_name=c.repo_name,revision=p_cs.short_id),title=p_cs.message)} </div> - %endfor + %endfor + <span class="logtags"> + <span class="branchtag" title="${'%s %s' % (_('branch'),cs.branch)}"> + ${h.link_to(cs.branch,h.url('files_home',repo_name=c.repo_name,revision=cs.short_id))}</span> + %for tag in cs.tags: + <span class="tagtag" title="${'%s %s' % (_('tag'),tag)}"> + ${h.link_to(tag,h.url('files_home',repo_name=c.repo_name,revision=cs.short_id))}</span> + %endfor + </span> </div> </div>
--- a/pylons_app/templates/changeset/changeset.html Tue Sep 28 20:38:35 2010 +0000 +++ b/pylons_app/templates/changeset/changeset.html Sun Oct 03 02:27:44 2010 +0200 @@ -1,7 +1,7 @@ <%inherit file="/base/base.html"/> <%def name="title()"> - ${_('Changeset')} + ${_('Changeset')} - r${c.changeset.revision}:${c.changeset.short_id} - ${c.repo_name} </%def> <%def name="breadcrumbs_links()"> @@ -34,40 +34,56 @@ </div> </div> </div> - - <div id="changeset_content"> - <div class="container"> - <div class="left"> - <div class="date">${_('Date')}: ${c.changeset.date}</div> - <div class="author">${_('Author')}: ${c.changeset.author}</div> - <div class="message">${h.wrap_paragraphs(c.changeset.message)}</div> - </div> - <div class="right"> - <span class="logtags"> - <span class="branchtag">${c.changeset.branch}</span> - %for tag in c.changeset.tags: - <span class="tagtag">${tag}</span> - %endfor - </span> - %if len(c.changeset.parents)>1: - <div class="merge"> - ${_('merge')} - <img alt="merge" src="/images/icons/arrow_join.png"> - </div> - %endif - %for p_cs in reversed(c.changeset.parents): - <div class="parent">${_('Parent')} ${p_cs.revision}: ${h.link_to(p_cs.short_id, - h.url('changeset_home',repo_name=c.repo_name,revision=p_cs.short_id),title=p_cs.message)} - </div> - %endfor - </div> - </div> - <span style="font-size:1.1em;font-weight: bold">${_('Files affected')}</span> - <div class="cs_files"> - %for change,filenode,diff,cs1,cs2 in c.changes: - <div class="cs_${change}">${h.link_to(filenode.path,h.url.current(anchor='CHANGE-%s'%filenode.path))}</div> - %endfor - </div> + <div id="changeset_content"> + <div class="container"> + <div class="left"> + <div class="date">${_('commit')} ${c.changeset.revision}: ${c.changeset.short_id}@${c.changeset.date}</div> + <div class="author"> + <div class="gravatar"> + <img alt="gravatar" src="${h.gravatar_url(h.email(c.changeset.author),20)}"/> + </div> + <span>${h.person(c.changeset.author)}</span><br/> + <span><a href="mailto:${h.email_or_none(c.changeset.author)}">${h.email_or_none(c.changeset.author)}</a></span><br/> + </div> + <div class="message"> + ${h.link_to(h.wrap_paragraphs(c.changeset.message), + h.url('changeset_home',repo_name=c.repo_name,revision=c.changeset.short_id))} + </div> + </div> + <div class="right"> + <div class="changes"> + <span class="removed" title="${_('removed')}">${len(c.changeset.removed)}</span> + <span class="changed" title="${_('changed')}">${len(c.changeset.changed)}</span> + <span class="added" title="${_('added')}">${len(c.changeset.added)}</span> + </div> + %if len(c.changeset.parents)>1: + <div class="merge"> + ${_('merge')}<img alt="merge" src="/images/icons/arrow_join.png"/> + </div> + %endif + %for p_cs in reversed(c.changeset.parents): + <div class="parent">${_('Parent')} ${p_cs.revision}: ${h.link_to(p_cs.short_id, + h.url('changeset_home',repo_name=c.repo_name,revision=p_cs.short_id),title=p_cs.message)} + </div> + %endfor + <span class="logtags"> + <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.short_id))}</span> + %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.short_id))}</span> + %endfor + </span> + </div> + </div> + <span style="font-size:1.1em;font-weight: bold">${_('Files affected')}</span> + <div class="cs_files"> + %for change,filenode,diff,cs1,cs2 in c.changes: + <div class="cs_${change}">${h.link_to(filenode.path,h.url.current(anchor='CHANGE-%s'%filenode.path))}</div> + %endfor + </div> + </div> + </div> %for change,filenode,diff,cs1,cs2 in c.changes:
--- a/pylons_app/templates/index.html Tue Sep 28 20:38:35 2010 +0000 +++ b/pylons_app/templates/index.html Sun Oct 03 02:27:44 2010 +0200 @@ -31,7 +31,7 @@ %if h.HasPermissionAny('hg.admin','hg.create.repository')(): <ul class="links"> <li> - <span>${h.link_to(u'ADD NEW REPOSITORY',h.url('admin_settings_create_repository'),class_="add_icon")}</span> + <span>${h.link_to(_('ADD NEW REPOSITORY'),h.url('admin_settings_create_repository'),class_="add_icon")}</span> </li> </ul> %endif @@ -61,7 +61,14 @@ <img class="icon" alt="${_('public')}" src="/images/icons/lock_open.png"/> %endif ${h.link_to(repo['name'], - h.url('summary_home',repo_name=repo['name']))}</td> + h.url('summary_home',repo_name=repo['name']))} + %if repo['repo'].dbrepo.fork: + <a href="${h.url('summary_home',repo_name=repo['repo'].dbrepo.fork.repo_name)}"> + <img class="icon" alt="${_('public')}" + title="${_('Fork of')} ${repo['repo'].dbrepo.fork.repo_name}" + src="/images/icons/arrow_divide.png"/></a> + %endif + </td> <td title="${repo['description']}">${h.truncate(repo['description'],60)}</td> <td>${h.age(repo['last_change'])}</td> <td>
--- a/pylons_app/templates/search/search.html Tue Sep 28 20:38:35 2010 +0000 +++ b/pylons_app/templates/search/search.html Sun Oct 03 02:27:44 2010 +0200 @@ -1,7 +1,13 @@ ## -*- coding: utf-8 -*- <%inherit file="/base/base.html"/> <%def name="title()"> - ${_('Search')}: ${c.cur_query} + ${_('Search')} + %if c.repo_name: + ${_('in repository: ') + c.repo_name} + %else: + ${_('in all repositories')} + %endif + :${c.cur_query} </%def> <%def name="breadcrumbs()"> ${c.hg_app_name} @@ -14,16 +20,26 @@ <div class="box"> <!-- box / title --> <div class="title"> - <h5>${_('Search')}</h5> + <h5>${_('Search')} + %if c.repo_name: + ${_('in repository: ') + c.repo_name} + %else: + ${_('in all repositories')} + %endif + </h5> </div> <!-- end box / title --> - ${h.form('search',method='get')} + %if c.repo_name: + ${h.form(h.url('search_repo',search_repo=c.repo_name),method='get')} + %else: + ${h.form(h.url('search'),method='get')} + %endif <div class="form"> <div class="fields"> <div class="field "> <div class="label"> - <label for="q">${_('Search:')}</label> + <label for="q">${_('Search')}:</label> </div> <div class="input"> ${h.text('q',c.cur_query,class_="small")}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/pylons_app/templates/settings/repo_fork.html Sun Oct 03 02:27:44 2010 +0200 @@ -0,0 +1,58 @@ +## -*- coding: utf-8 -*- +<%inherit file="/base/base.html"/> + +<%def name="title()"> + ${_('Fork repository')} ${c.repo_info.repo_name} +</%def> + +<%def name="breadcrumbs_links()"> + ${h.link_to(c.repo_info.repo_name,h.url('summary_home',repo_name=c.repo_info.repo_name))} + » + ${_('fork')} +</%def> + +<%def name="page_nav()"> + ${self.menu('')} +</%def> +<%def name="main()"> +<div class="box"> + <!-- box / title --> + <div class="title"> + ${self.breadcrumbs()} + </div> + ${h.form(url('repo_fork_create_home',repo_name=c.repo_info.repo_name))} + <div class="form"> + <!-- fields --> + <div class="fields"> + <div class="field"> + <div class="label"> + <label for="repo_name">${_('Fork name')}:</label> + </div> + <div class="input"> + ${h.text('fork_name')} + </div> + </div> + <div class="field"> + <div class="label label-textarea"> + <label for="description">${_('Description')}:</label> + </div> + <div class="textarea text-area editor"> + ${h.textarea('description',cols=23,rows=5)} + </div> + </div> + <div class="field"> + <div class="label label-checkbox"> + <label for="private">${_('Private')}:</label> + </div> + <div class="checkboxes"> + ${h.checkbox('private',value="True")} + </div> + </div> + <div class="buttons"> + ${h.submit('','fork this repository',class_="ui-button ui-widget ui-state-default ui-corner-all")} + </div> + </div> + </div> + ${h.end_form()} +</div> +</%def>
--- a/pylons_app/templates/settings/repo_settings.html Tue Sep 28 20:38:35 2010 +0000 +++ b/pylons_app/templates/settings/repo_settings.html Sun Oct 03 02:27:44 2010 +0200 @@ -2,7 +2,7 @@ <%inherit file="/base/base.html"/> <%def name="title()"> - ${_('Repositories administration')} + ${_('Repository settings')} </%def> <%def name="breadcrumbs_links()">
--- a/pylons_app/templates/summary/summary.html Tue Sep 28 20:38:35 2010 +0000 +++ b/pylons_app/templates/summary/summary.html Sun Oct 03 02:27:44 2010 +0200 @@ -42,7 +42,23 @@ <label>${_('Name')}:</label> </div> <div class="input-short"> - <span style="font-size: 1.6em;font-weight: bold">${c.repo_info.name}</span> + %if c.repo_info.dbrepo.private: + <img style="margin-bottom:2px" class="icon" title="${_('private repository')}" alt="${_('private')}" src="/images/icons/lock.png"/> + %else: + <img style="margin-bottom:2px" class="icon" title="${_('public repository')}" alt="${_('public')}" src="/images/icons/lock_open.png"/> + %endif + <span style="font-size: 1.6em;font-weight: bold;vertical-align: baseline;">${c.repo_info.name}</span> + <br/> + %if c.repo_info.dbrepo.fork: + <span style="margin-top:5px"> + <a href="${h.url('summary_home',repo_name=c.repo_info.dbrepo.fork.repo_name)}"> + <img class="icon" alt="${_('public')}" + title="${_('Fork of')} ${c.repo_info.dbrepo.fork.repo_name}" + src="/images/icons/arrow_divide.png"/> + ${_('Fork of')} ${c.repo_info.dbrepo.fork.repo_name} + </a> + </span> + %endif </div> </div> @@ -76,7 +92,7 @@ <label>${_('Last change')}:</label> </div> <div class="input-short"> - ${h.age(c.repo_info.last_change)} - ${h.rfc822date(c.repo_info.last_change)} + ${h.age(c.repo_info.last_change)} - ${h.rfc822date_notz(c.repo_info.last_change)} ${_('by')} ${h.get_changeset_safe(c.repo_info,'tip').author} </div> @@ -122,7 +138,7 @@ var td2 = document.createElement('td'); var trending_language = document.createElement('div'); trending_language.title = k; - trending_language.innerHTML = "<b>"+value+" ${_('files')} - "+percentage+" %</b>"; + trending_language.innerHTML = "<b>"+percentage+"% "+value+" ${_('files')}</b>"; trending_language.setAttribute("class", 'trending_language'); trending_language.style.width=percentage+"%"; td2.appendChild(trending_language); @@ -130,7 +146,6 @@ tr.appendChild(td1); tr.appendChild(td2); tbl.appendChild(tr); - //YAHOO.util.Dom.get('lang_stats').appendChild(trending_language_label); } if(no_data){
--- a/pylons_app/tests/__init__.py Tue Sep 28 20:38:35 2010 +0000 +++ b/pylons_app/tests/__init__.py Sun Oct 03 02:27:44 2010 +0200 @@ -28,6 +28,8 @@ # Invoke websetup with the current config file #SetupCommand('setup-app').run([config_file]) +##RUNNING DESIRED TESTS +#nosetests pylons_app.tests.functional.test_admin_settings:TestSettingsController.test_my_account environ = {} @@ -42,10 +44,15 @@ TestCase.__init__(self, *args, **kwargs) - def log_user(self, username='test_admin', password='test'): + def log_user(self, username='test_admin', password='test12'): response = self.app.post(url(controller='login', action='index'), {'username':username, 'password':password}) + print response + + if 'invalid user name' in response.body: + assert False, 'could not login using %s %s' % (username, password) + assert response.status == '302 Found', 'Wrong response code from login got %s' % response.status - assert response.session['hg_app_user'].username == 'test_admin', 'wrong logged in user' + assert response.session['hg_app_user'].username == username, 'wrong logged in user got %s expected %s' % (response.session['hg_app_user'].username, username) return response.follow()
--- a/pylons_app/tests/functional/test_admin_settings.py Tue Sep 28 20:38:35 2010 +0000 +++ b/pylons_app/tests/functional/test_admin_settings.py Sun Oct 03 02:27:44 2010 +0200 @@ -57,7 +57,7 @@ response = self.app.post(url('admin_settings_my_account_update'), params=dict( _method='put', username='test_admin', - new_password='test', + new_password='test12', password='', name='NewName', lastname='NewLastname', @@ -78,7 +78,7 @@ response = self.app.post(url('admin_settings_my_account_update'), params=dict( _method='put', username='test_admin', - new_password='test', + new_password='test12', name='NewName', lastname='NewLastname', email=new_email,)) @@ -91,7 +91,7 @@ response = self.app.post(url('admin_settings_my_account_update'), params=dict( _method='put', username='test_admin', - new_password='test', + new_password='test12', name='NewName', lastname='NewLastname', email=new_email,)) @@ -101,13 +101,13 @@ def test_my_account_update_err(self): - self.log_user() + self.log_user('test_regular2', 'test12') new_email = 'newmail.pl' response = self.app.post(url('admin_settings_my_account_update'), params=dict( _method='put', - username='test_regular2', - new_password='test', + username='test_admin', + new_password='test12', name='NewName', lastname='NewLastname', email=new_email,))
--- a/pylons_app/tests/functional/test_login.py Tue Sep 28 20:38:35 2010 +0000 +++ b/pylons_app/tests/functional/test_login.py Sun Oct 03 02:27:44 2010 +0200 @@ -13,7 +13,7 @@ def test_login_admin_ok(self): response = self.app.post(url(controller='login', action='index'), {'username':'test_admin', - 'password':'test'}) + 'password':'test12'}) assert response.status == '302 Found', 'Wrong response code from login got %s' % response.status assert response.session['hg_app_user'].username == 'test_admin', 'wrong logged in user' response = response.follow() @@ -22,7 +22,8 @@ def test_login_regular_ok(self): response = self.app.post(url(controller='login', action='index'), {'username':'test_regular', - 'password':'test'}) + 'password':'test12'}) + print response assert response.status == '302 Found', 'Wrong response code from login got %s' % response.status assert response.session['hg_app_user'].username == 'test_regular', 'wrong logged in user' response = response.follow() @@ -33,7 +34,7 @@ test_came_from = '/_admin/users' response = self.app.post(url(controller='login', action='index', came_from=test_came_from), {'username':'test_admin', - 'password':'test'}) + 'password':'test12'}) assert response.status == '302 Found', 'Wrong response code from came from redirection' response = response.follow() @@ -41,15 +42,23 @@ assert 'Users administration' in response.body, 'No proper title in response' - def test_login_wrong(self): + def test_login_short_password(self): response = self.app.post(url(controller='login', action='index'), {'username':'error', 'password':'test'}) assert response.status == '200 OK', 'Wrong response from login page' + assert 'Enter a value 6 characters long or more' in response.body, 'No error password message in response' + + def test_login_wrong_username_password(self): + response = self.app.post(url(controller='login', action='index'), + {'username':'error', + 'password':'test12'}) + assert response.status == '200 OK', 'Wrong response from login page' + assert 'invalid user name' in response.body, 'No error username message in response' assert 'invalid password' in response.body, 'No error password message in response' - + def test_register(self): response = self.app.get(url(controller='login', action='register')) @@ -76,8 +85,7 @@ assert response.status == '200 OK', 'Wrong response from register page got %s' % response.status assert 'An email address must contain a single @' in response.body - assert 'Enter a value 3 characters long or more' in response.body - assert 'Please enter a value<' in response.body + assert 'Please enter a value' in response.body
--- a/pylons_app/tests/functional/test_settings.py Tue Sep 28 20:38:35 2010 +0000 +++ b/pylons_app/tests/functional/test_settings.py Sun Oct 03 02:27:44 2010 +0200 @@ -1,3 +1,4 @@ +from pylons_app.model.db import Repository from pylons_app.tests import * class TestSettingsController(TestController): @@ -7,3 +8,48 @@ response = self.app.get(url(controller='settings', action='index', repo_name='vcs_test')) # Test response... + + def test_fork(self): + self.log_user() + response = self.app.get(url(controller='settings', action='fork', + repo_name='vcs_test')) + + + def test_fork_create(self): + self.log_user() + fork_name = 'vcs_test_fork' + description = 'fork of vcs test' + repo_name = 'vcs_test' + response = self.app.post(url(controller='settings', action='fork_create', + repo_name=repo_name), + {'fork_name':fork_name, + 'description':description, + 'private':'False'}) + + + print response + + #test if we have a message that fork is ok + assert 'fork %s repository as %s task added' \ + % (repo_name, fork_name) in response.session['flash'][0], 'No flash message about fork' + + #test if the fork was created in the database + fork_repo = self.sa.query(Repository).filter(Repository.repo_name == fork_name).one() + + assert fork_repo.repo_name == fork_name, 'wrong name of repo name in new db fork repo' + assert fork_repo.fork.repo_name == repo_name, 'wron fork parrent' + + + #test if fork is visible in the list ? + response = response.follow() + + + #check if fork is marked as fork + response = self.app.get(url(controller='summary', action='index', + repo_name=fork_name)) + + + print response + + assert 'Fork of %s' % repo_name in response.body, 'no message about that this repo is a fork' +
--- a/setup.py Tue Sep 28 20:38:35 2010 +0000 +++ b/setup.py Sun Oct 03 02:27:44 2010 +0200 @@ -20,11 +20,11 @@ "SQLAlchemy>=0.6", "babel", "Mako>=0.3.2", - "vcs>=0.1.6", + "vcs>=0.1.7", "pygments>=1.3.0", "mercurial>=1.6", "pysqlite", - "whoosh==1.0.0b17", + "whoosh==1.0.0b20", "py-bcrypt", "celery", ],