Changeset - 3f5be4dbbd57
README.rst
Show inline comments
 
=================================================
 
Welcome to RhodeCode (RhodiumCode) documentation!
 
=================================================
 
========================
 
RhodeCode documentation!
 
========================
 

	
 
``RhodeCode`` is a fast and powerful management tool for Mercurial_ and GIT_ 
 
with a built in push/pull server and full text search.
 
It works on http/https and has a built in permission/authentication system with 
 
the ability to authenticate via LDAP or ActiveDirectory. RhodeCode also supports
 
simple API so it's easy integrable with existing external systems.
 

	
 
RhodeCode is similar in some respects to github or bitbucket_, 
 
however RhodeCode can be run as standalone hosted application on your own server.
 
It is open source and donation ware and focuses more on providing a customized, 
 
self administered interface for Mercurial and GIT repositories. 
 
RhodeCode is powered by a vcs_ library that Lukasz Balcerzak and I created to 
 
handle multiple different version control systems.
 

	
 
RhodeCode uses `Semantic Versioning <http://semver.org/>`_
 

	
 
RhodeCode demo
 
--------------
 

	
 
http://demo.rhodecode.org
 

	
 
The default access is anonymous but you can login to an administrative account
 
using the following credentials:
 

	
 
- username: demo
 
- password: demo12
 

	
 
Source code
 
-----------
 

	
 
The latest sources can be obtained from official RhodeCode instance
 
https://secure.rhodecode.org 
 

	
 

	
 
MIRRORS:
 

	
 
Issue tracker and sources at bitbucket_
 

	
 
http://bitbucket.org/marcinkuzminski/rhodecode
 

	
 
Sources at github_
 

	
 
https://github.com/marcinkuzminski/rhodecode
 

	
 
Installation
 
------------
 

	
 
Please visit http://packages.python.org/RhodeCode/installation.html
 

	
 

	
 
RhodeCode Features
 
------------------
 

	
 
- Has its own middleware to handle mercurial_ protocol requests. 
 
  Each request can be logged and authenticated.
 
- Runs on threads unlike hgweb. You can make multiple pulls/pushes simultaneous.
 
  Supports http/https and LDAP
 
- Full permissions (private/read/write/admin) and authentication per project. 
 
  One account for web interface and mercurial_ push/pull/clone operations.
 
- Have built in users groups for easier permission management
 
- Repository groups let you group repos and manage them easier.
 
- Users can fork other users repo. RhodeCode have also compare view to see
 
  combined changeset for all changeset made within single push.
 
- Build in commit-api let's you add, edit and commit files right from RhodeCode
 
  interface using simple editor or upload form for binaries.
 
- Mako templates let's you customize the look and feel of the application.
 
- Beautiful diffs, annotations and source code browsing all colored by pygments. 
 
  Raw diffs are made in git-diff format, including git_ binary-patches
 
- Mercurial_ branch graph and yui-flot powered graphs with zooming and statistics
 
- Admin interface with user/permission management. Admin activity journal, logs
 
  pulls, pushes, forks, registrations and other actions made by all users.
 
- Server side forks. It is possible to fork a project and modify it freely 
 
  without breaking the main repository. You can even write Your own hooks 
 
  and install them
 
- code review with notification system, inline commenting, all parsed using
 
  rst syntax
 
- rst and markdown README support for repositories  
 
- Full text search powered by Whoosh on the source files, and file names.
 
  Build in indexing daemons, with optional incremental index build
 
  (no external search servers required all in one application)
 
- Setup project descriptions and info inside built in db for easy, non 
 
  file-system operations
 
- Intelligent cache with invalidation after push or project change, provides 
 
  high performance and always up to date data.
 
- Rss / atom feeds, gravatar support, download sources as zip/tar/gz
 
- Async tasks for speed and performance using celery_ (works without them too)  
 
- Backup scripts can do backup of whole app and send it over scp to desired 
 
  location 
 
- Based on pylons / sqlalchemy / sqlite / whoosh / vcs
 

	
 

	
 
.. include:: ./docs/screenshots.rst
 
    
 
    
 
Incoming / Plans
 
----------------
 

	
 
- Finer granular permissions per branch, repo group or subrepo
 
- pull requests and web based merges
 
- per line file history
 
- SSH based authentication with server side key management
 
- Redmine and other bugtrackers integration
 
- Commit based built in wiki system
 
- More statistics and graph (global annotation + some more statistics)
 
- Other advancements as development continues (or you can of course make 
 
  additions and or requests)
 

	
 
License
 
-------
 

	
 
``RhodeCode`` is released under the GPLv3 license.
 

	
 

	
 
Mailing group Q&A
 
-----------------
 

	
 
Join the `Google group <http://groups.google.com/group/rhodecode>`_
 

	
 
Open an issue at `issue tracker <http://bitbucket.org/marcinkuzminski/rhodecode/issues>`_
 

	
 
Join #rhodecode on FreeNode (irc.freenode.net)
 
or use http://webchat.freenode.net/?channels=rhodecode for web access to irc.
 

	
 
Online documentation
 
--------------------
 

	
 
Online documentation for the current version of RhodeCode is available at
 
http://packages.python.org/RhodeCode/.
 
You may also build the documentation for yourself - go into ``docs/`` and run::
 

	
 
   make html
 

	
 
(You need to have sphinx_ installed to build the documentation. If you don't
 
have sphinx_ installed you can install it via the command: 
 
``easy_install sphinx``)
 
 
 
.. _virtualenv: http://pypi.python.org/pypi/virtualenv
 
.. _python: http://www.python.org/
 
.. _sphinx: http://sphinx.pocoo.org/
 
.. _mercurial: http://mercurial.selenic.com/
 
.. _bitbucket: http://bitbucket.org/
 
.. _github: http://github.com/
 
.. _subversion: http://subversion.tigris.org/
 
.. _git: http://git-scm.com/
 
.. _celery: http://celeryproject.org/
 
.. _Sphinx: http://sphinx.pocoo.org/
 
.. _vcs: http://pypi.python.org/pypi/vcs
 
\ No newline at end of file
development.ini
Show inline comments
 
################################################################################
 
################################################################################
 
# RhodeCode - Pylons environment configuration                                 #
 
#                                                                              # 
 
# The %(here)s variable will be replaced with the parent directory of this file#
 
################################################################################
 

	
 
[DEFAULT]
 
debug = true
 
pdebug = false
 
################################################################################
 
## Uncomment and replace with the address which should receive                ## 
 
## any error reports after application crash                                  ##
 
## Additionally those settings will be used by RhodeCode mailing system       ##
 
################################################################################
 
#email_to = admin@localhost
 
#error_email_from = paste_error@localhost
 
#app_email_from = rhodecode-noreply@localhost
 
#error_message =
 
#email_prefix = [RhodeCode]
 

	
 
#smtp_server = mail.server.com
 
#smtp_username = 
 
#smtp_password = 
 
#smtp_port = 
 
#smtp_use_tls = false
 
#smtp_use_ssl = true
 
# Specify available auth parameters here (e.g. LOGIN PLAIN CRAM-MD5, etc.)
 
#smtp_auth = 
 

	
 
[server:main]
 
##nr of threads to spawn
 
threadpool_workers = 5
 

	
 
##max request before thread respawn
 
threadpool_max_requests = 10
 

	
 
##option to use threads of process
 
use_threadpool = true
 

	
 
use = egg:Paste#http
 
host = 0.0.0.0
 
port = 5000
 

	
 
[app:main]
 
use = egg:rhodecode
 
full_stack = true
 
static_files = true
 
lang=en
 
cache_dir = %(here)s/data
 
index_dir = %(here)s/data/index
 
app_instance_uuid = develop
 
cut_off_limit = 256000
 
force_https = false
 
commit_parse_limit = 25
 
use_gravatar = true
 
container_auth_enabled = false
 
proxypass_auth_enabled = false
 

	
 
## overwrite schema of clone url
 
# available vars:
 
# scheme - http/https
 
# user - current user
 
# pass - password 
 
# netloc - network location
 
# path - usually repo_name
 
# clone_uri = {scheme}://{user}{pass}{netloc}{path}
 
## available vars:
 
## scheme - http/https
 
## user - current user
 
## pass - password 
 
## netloc - network location
 
## path - usually repo_name
 

	
 
#clone_uri = {scheme}://{user}{pass}{netloc}{path}
 

	
 
## issue tracking mapping for commits messages
 
## uncomment url_pat, issue_server, issue_prefix to enable
 

	
 

	
 
## pattern to get the issues from commit messages
 
## default one used here is #1234
 

	
 
#url_pat = (?:^#|\s#)(\w+)
 

	
 
## server url to the issue, each {id} will be replaced with id
 
## fetched from the regex
 

	
 
#issue_server = https://myissueserver.com/issue/{id}
 

	
 
## prefix to add to link to indicate it's an url
 
## #314 will be replaced by <issue_prefix><id>
 

	
 
#issue_prefix = #
 

	
 

	
 
####################################
 
###        CELERY CONFIG        ####
 
####################################
 
use_celery = false
 
broker.host = localhost
 
broker.vhost = rabbitmqhost
 
broker.port = 5672
 
broker.user = rabbitmq
 
broker.password = qweqwe
 

	
 
celery.imports = rhodecode.lib.celerylib.tasks
 

	
 
celery.result.backend = amqp
 
celery.result.dburi = amqp://
 
celery.result.serialier = json
 

	
 
#celery.send.task.error.emails = true
 
#celery.amqp.task.result.expires = 18000
 

	
 
celeryd.concurrency = 2
 
#celeryd.log.file = celeryd.log
 
celeryd.log.level = debug
 
celeryd.max.tasks.per.child = 1
 

	
 
#tasks will never be sent to the queue, but executed locally instead.
 
celery.always.eager = false
 

	
 
####################################
 
###         BEAKER CACHE        ####
 
####################################
 
beaker.cache.data_dir=%(here)s/data/cache/data
 
beaker.cache.lock_dir=%(here)s/data/cache/lock
 

	
 
beaker.cache.regions=super_short_term,short_term,long_term,sql_cache_short,sql_cache_med,sql_cache_long
 

	
 
beaker.cache.super_short_term.type=memory
 
beaker.cache.super_short_term.expire=10
 
beaker.cache.super_short_term.key_length = 256
 

	
 
beaker.cache.short_term.type=memory
 
beaker.cache.short_term.expire=60
 
beaker.cache.short_term.key_length = 256
 

	
 
beaker.cache.long_term.type=memory
 
beaker.cache.long_term.expire=36000
 
beaker.cache.long_term.key_length = 256
 

	
 
beaker.cache.sql_cache_short.type=memory
 
beaker.cache.sql_cache_short.expire=10
 
beaker.cache.sql_cache_short.key_length = 256
 

	
 
beaker.cache.sql_cache_med.type=memory
 
beaker.cache.sql_cache_med.expire=360
 
beaker.cache.sql_cache_med.key_length = 256
 

	
 
beaker.cache.sql_cache_long.type=file
 
beaker.cache.sql_cache_long.expire=3600
 
beaker.cache.sql_cache_long.key_length = 256
 

	
 
####################################
 
###       BEAKER SESSION        ####
 
####################################
 
## Type of storage used for the session, current types are 
 
## dbm, file, memcached, database, and memory. 
 
## The storage uses the Container API 
 
## that is also used by the cache system.
 

	
 
## db session example
 

	
 
#beaker.session.type = ext:database
 
#beaker.session.sa.url = postgresql://postgres:qwe@localhost/rhodecode
 
#beaker.session.table_name = db_session 
 

	
 
## encrypted cookie session, good for many instances
 
#beaker.session.type = cookie
 

	
 
beaker.session.type = file
 
beaker.session.key = rhodecode
 
#beaker.session.encrypt_key = g654dcno0-9873jhgfreyu
 
#beaker.session.validate_key = 9712sds2212c--zxc123
 
beaker.session.timeout = 36000
 
beaker.session.httponly = true
 

	
 
## uncomment for https secure cookie
 
beaker.session.secure = false
 

	
 
##auto save the session to not to use .save()
 
beaker.session.auto = False
 

	
 
##true exire at browser close
 
#beaker.session.cookie_expires = 3600
 

	
 
    
 
################################################################################
 
## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT*  ##
 
## Debug mode will enable the interactive debugging tool, allowing ANYONE to  ##
 
## execute malicious code after an exception is raised.                       ##
 
################################################################################
 
#set debug = false
 

	
 
##################################
 
###       LOGVIEW CONFIG       ###
 
##################################
 
logview.sqlalchemy = #faa
 
logview.pylons.templating = #bfb
 
logview.pylons.util = #eee
 

	
 
#########################################################
 
### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG    ###
 
#########################################################
 
#sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db
 
sqlalchemy.db1.url = postgresql://postgres:qwe@localhost/rhodecode
 
sqlalchemy.db1.echo = false
 
sqlalchemy.db1.pool_recycle = 3600
 
sqlalchemy.convert_unicode = true
 

	
 
################################
 
### LOGGING CONFIGURATION   ####
 
################################
 
[loggers]
 
keys = root, routes, rhodecode, sqlalchemy, beaker, templates
 

	
 
[handlers]
 
keys = console, console_sql
 

	
 
[formatters]
 
keys = generic, color_formatter, color_formatter_sql
 

	
 
#############
 
## LOGGERS ##
 
#############
 
[logger_root]
 
level = NOTSET
 
handlers = console
 

	
 
[logger_routes]
 
level = DEBUG
 
handlers = 
 
qualname = routes.middleware
 
# "level = DEBUG" logs the route matched and routing variables.
 
propagate = 1
 

	
 
[logger_beaker]
 
level = DEBUG
 
handlers = 
 
qualname = beaker.container
 
propagate = 1
 

	
 
[logger_templates]
 
level = INFO
 
handlers = 
 
qualname = pylons.templating
 
propagate = 1
 

	
 
[logger_rhodecode]
 
level = DEBUG
 
handlers = 
 
qualname = rhodecode
 
propagate = 1
 

	
 
[logger_sqlalchemy]
 
level = INFO
 
handlers = console_sql
 
qualname = sqlalchemy.engine
 
propagate = 0
 

	
 
##############
 
## HANDLERS ##
 
##############
 

	
 
[handler_console]
 
class = StreamHandler
 
args = (sys.stderr,)
 
level = DEBUG
 
formatter = color_formatter
 

	
 
[handler_console_sql]
 
class = StreamHandler
 
args = (sys.stderr,)
 
level = DEBUG
 
formatter = color_formatter_sql
 

	
 
################
 
## FORMATTERS ##
 
################
 

	
 
[formatter_generic]
 
format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
 
datefmt = %Y-%m-%d %H:%M:%S
 

	
 
[formatter_color_formatter]
docs/api/api.rst
Show inline comments
 
.. _api:
 

	
 

	
 
API
 
===
 

	
 

	
 
Starting from RhodeCode version 1.2 a simple API was implemented.
 
There's a single schema for calling all api methods. API is implemented
 
with JSON protocol both ways. An url to send API request in RhodeCode is
 
<your_server>/_admin/api
 

	
 
API ACCESS FOR WEB VIEWS
 
++++++++++++++++++++++++
 

	
 
API access can also be turned on for each view decorated with `@LoginRequired`
 
decorator. To enable API access simple change standard login decorator into
 
`@LoginRequired(api_access=True)`. After such a change view can be accessed
 
by adding a GET parameter to url `?api_key=<api_key>`. By default it's only
 
enabled on RSS/ATOM feed views.
 

	
 

	
 
API ACCESS
 
++++++++++
 

	
 
All clients are required to send JSON-RPC spec JSON data::
 

	
 
    {   
 
        "id:<id>,
 
        "api_key":"<api_key>",
 
        "method":"<method_name>",
 
        "args":{"<arg_key>":"<arg_val>"}
 
    }
 

	
 
Example call for autopulling remotes repos using curl::
 
    curl https://server.com/_admin/api -X POST -H 'content-type:text/plain' --data-binary '{"id":1,"api_key":"xe7cdb2v278e4evbdf5vs04v832v0efvcbcve4a3","method":"pull","args":{"repo":"CPython"}}'
 

	
 
Simply provide
 
 - *id* A value of any type, which is used to match the response with the request that it is replying to.
 
 - *api_key* for access and permission validation.
 
 - *method* is name of method to call
 
 - *args* is an key:value list of arguments to pass to method
 

	
 
.. note::
 

	
 
    api_key can be found in your user account page
 

	
 

	
 
RhodeCode API will return always a JSON-RPC response::
 

	
 
    {   
 
        "id":<id>,
 
        "result": "<result>",
 
        "error": null
 
    }
 

	
 
All responses from API will be `HTTP/1.0 200 OK`, if there's an error while
 
calling api *error* key from response will contain failure description
 
and result will be null.
 

	
 
API METHODS
 
+++++++++++
 

	
 

	
 
pull
 
----
 

	
 
Pulls given repo from remote location. Can be used to automatically keep
 
remote repos up to date. This command can be executed only using api_key
 
belonging to user with admin rights
 

	
 
INPUT::
 

	
 
    api_key : "<api_key>"
 
    method :  "pull"
 
    args :    {
 
                "repo" : "<repo_name>"
 
                "repo_name" : "<reponame>"
 
              }
 

	
 
OUTPUT::
 

	
 
    result : "Pulled from <repo_name>"
 
    result : "Pulled from <reponame>"
 
    error :  null
 

	
 

	
 
get_user
 
--------
 

	
 
Get's an user by username, Returns empty result if user is not found.
 
This command can be executed only using api_key belonging to user with admin 
 
rights.
 

	
 
INPUT::
 

	
 
    api_key : "<api_key>"
 
    method :  "get_user"
 
    args :    { 
 
                "username" : "<username>"
 
              }
 

	
 
OUTPUT::
 

	
 
    result: None if user does not exist or 
 
            {
 
                "id" :       "<id>",
 
                "username" : "<username>",
 
                "firstname": "<firstname>",
 
                "lastname" : "<lastname>",
 
                "email" :    "<email>",
 
                "active" :   "<bool>",
 
                "admin" :    "<bool>",
 
                "ldap" :     "<ldap_dn>"
 
            }
 

	
 
    error:  null
 

	
 

	
 
get_users
 
---------
 

	
 
Lists all existing users. This command can be executed only using api_key
 
belonging to user with admin rights.
 

	
 
INPUT::
 

	
 
    api_key : "<api_key>"
 
    method :  "get_users"
 
    args :    { }
 

	
 
OUTPUT::
 

	
 
    result: [
 
              {
 
                "id" :       "<id>",
 
                "username" : "<username>",
 
                "firstname": "<firstname>",
 
                "lastname" : "<lastname>",
 
                "email" :    "<email>",
 
                "active" :   "<bool>",
 
                "admin" :    "<bool>",
 
                "ldap" :     "<ldap_dn>"
 
              },
 
    	      …
 
            ]
 
    error:  null
 

	
 
create_user
 
-----------
 

	
 
Creates new user in RhodeCode. This command can be executed only using api_key
 
belonging to user with admin rights.
 

	
 
INPUT::
 

	
 
    api_key : "<api_key>"
 
    method :  "create_user"
 
    args :    {
 
                "username" :  "<username>",
 
                "password" :  "<password>",
 
                "firstname" : "<firstname>",
 
                "lastname" :  "<lastname>",
 
                "email" :     "<useremail>"
 
                "active" :    "<bool> = True",
 
                "admin" :     "<bool> = False",
 
                "ldap_dn" :   "<ldap_dn> = None"
 
              }
 

	
 
OUTPUT::
 

	
 
    result: {
 
              "id" : "<new_user_id>",
 
              "msg" : "created new user <username>"
 
            }
 
    error:  null
 

	
 
get_users_groups
 
----------------
 

	
 
Lists all existing users groups. This command can be executed only using api_key
 
belonging to user with admin rights.
 

	
 
INPUT::
 

	
 
    api_key : "<api_key>"
 
    method :  "get_users_groups"
 
    args :    { }
 

	
 
OUTPUT::
 

	
 
    result : [
 
               {
 
                 "id" :       "<id>",
 
                 "name" :     "<name>",
 
                 "active":    "<bool>",
 
                 "members" :  [
 
	    	                    {
 
	    	                      "id" :       "<userid>",
 
	                              "username" : "<username>",
 
	                              "firstname": "<firstname>",
 
	                              "lastname" : "<lastname>",
 
	                              "email" :    "<email>",
 
	                              "active" :   "<bool>",
 
	                              "admin" :    "<bool>",
 
	                              "ldap" :     "<ldap_dn>"
 
	                            },
 
	    	                    …
 
	                          ]
 
	            }
 
              ]
 
    error : null
 

	
 
get_users_group
 
---------------
 

	
 
Gets an existing users group. This command can be executed only using api_key
 
belonging to user with admin rights.
 

	
 
INPUT::
 

	
 
    api_key : "<api_key>"
 
    method :  "get_users_group"
 
    args :    {
 
                "group_name" : "<name>"
 
              }
 

	
 
OUTPUT::
 

	
 
    result : None if group not exist
 
             {
 
               "id" :       "<id>",
 
               "name" :     "<name>",
 
               "active":    "<bool>",
 
               "id" :         "<id>",
 
               "group_name" : "<groupname>",
 
               "active":      "<bool>",
 
               "members" :  [
 
	    	                  { "id" :       "<userid>",
 
	                            "username" : "<username>",
 
	                            "firstname": "<firstname>",
 
	                            "lastname" : "<lastname>",
 
	                            "email" :    "<email>",
 
	                            "active" :   "<bool>",
 
	                            "admin" :    "<bool>",
 
	                            "ldap" :     "<ldap_dn>"
 
	                          },
 
	    	                  …
 
	                        ]
 
                              { "id" :       "<userid>",
 
                                "username" : "<username>",
 
                                "firstname": "<firstname>",
 
                                "lastname" : "<lastname>",
 
                                "email" :    "<email>",
 
                                "active" :   "<bool>",
 
                                "admin" :    "<bool>",
 
                                "ldap" :     "<ldap_dn>"
 
                              },
 
                              …
 
                            ]
 
             }
 
    error : null
 

	
 
get_users_groups
 
----------------
 

	
 
Lists all existing users groups. This command can be executed only using 
 
api_key belonging to user with admin rights.
 

	
 
INPUT::
 

	
 
    api_key : "<api_key>"
 
    method :  "get_users_groups"
 
    args :    { }
 

	
 
OUTPUT::
 

	
 
    result : [
 
               {
 
                 "id" :         "<id>",
 
                 "group_name" : "<groupname>",
 
                 "active":      "<bool>",
 
                 "members" :  [
 
	    	                    {
 
	    	                      "id" :       "<userid>",
 
	                              "username" : "<username>",
 
	                              "firstname": "<firstname>",
 
	                              "lastname" : "<lastname>",
 
	                              "email" :    "<email>",
 
	                              "active" :   "<bool>",
 
	                              "admin" :    "<bool>",
 
	                              "ldap" :     "<ldap_dn>"
 
	                            },
 
	    	                    …
 
	                          ]
 
	            }
 
              ]
 
    error : null
 

	
 

	
 
create_users_group
 
------------------
 

	
 
Creates new users group. This command can be executed only using api_key
 
belonging to user with admin rights
 

	
 
INPUT::
 

	
 
    api_key : "<api_key>"
 
    method :  "create_users_group"
 
    args:     {
 
                "name":  "<name>",
 
                "group_name":  "<groupname>",
 
                "active":"<bool> = True"
 
              }
 

	
 
OUTPUT::
 

	
 
    result: {
 
              "id":  "<newusersgroupid>",
 
              "msg": "created new users group <name>"
 
              "msg": "created new users group <groupname>"
 
            }
 
    error:  null
 

	
 
add_user_to_users_group
 
-----------------------
 

	
 
Adds a user to a users group. This command can be executed only using api_key
 
belonging to user with admin rights
 

	
 
INPUT::
 

	
 
    api_key : "<api_key>"
 
    method :  "add_user_users_group"
 
    args:     {
 
                "group_name" :  "<groupname>",
 
                "username" :   "<username>"
 
              }
 

	
 
OUTPUT::
 

	
 
    result: {
 
              "id":  "<newusersgroupmemberid>",
 
              "msg": "created new users group member"
 
            }
 
    error:  null
 

	
 
get_repo
 
--------
 

	
 
Gets an existing repository. This command can be executed only using api_key
 
belonging to user with admin rights
 

	
 
INPUT::
 

	
 
    api_key : "<api_key>"
 
    method :  "get_repo"
 
    args:     {
 
                "repo_name" : "<reponame>"
 
              }
 

	
 
OUTPUT::
 

	
 
    result: None if repository does not exist or
 
            {
 
                "id" :          "<id>",
 
                "repo_name" :   "<reponame>"
 
                "type" :        "<type>",
 
                "description" : "<description>",
 
                "members" :     [
 
                                  { "id" :         "<userid>",
 
                                    "username" :   "<username>",
 
                                    "firstname":   "<firstname>",
 
                                    "lastname" :   "<lastname>",
 
                                    "email" :      "<email>",
 
                                    "active" :     "<bool>",
 
                                    "admin" :      "<bool>",
 
                                    "ldap" :       "<ldap_dn>",
 
                                    "permission" : "repository.(read|write|admin)"
 
                                  },
 
                                  …
 
                                  {
 
                                    "id" :       "<usersgroupid>",
 
                                    "name" :     "<usersgroupname>",
 
                                    "active":    "<bool>",
 
                                    "permission" : "repository.(read|write|admin)"
 
                                  },
 
                                  …
 
                                ]
 
            }
 
    error:  null
 

	
 
get_repos
 
---------
 

	
 
Lists all existing repositories. This command can be executed only using api_key
 
belonging to user with admin rights
 

	
 
INPUT::
 

	
 
    api_key : "<api_key>"
 
    method :  "get_repos"
 
    args:     { }
 

	
 
OUTPUT::
 

	
 
    result: [
 
              {
 
                "id" :          "<id>",
 
                "name" :        "<name>"
 
                "repo_name" :   "<reponame>"
 
                "type" :        "<type>",
 
                "description" : "<description>"
 
              },
 
              …
 
            ]
 
    error:  null
 

	
 
get_repo
 
--------
 

	
 
Gets an existing repository. This command can be executed only using api_key
 
belonging to user with admin rights
 

	
 
INPUT::
 

	
 
    api_key : "<api_key>"
 
    method :  "get_repo"
 
    args:     {
 
                "name" : "<name>"
 
              }
 

	
 
OUTPUT::
 

	
 
    result: None if repository not exist
 
            {
 
                "id" :          "<id>",
 
                "name" :        "<name>"
 
                "type" :        "<type>",
 
                "description" : "<description>",
 
                "members" :     [
 
                                  { "id" :         "<userid>",
 
	                                "username" :   "<username>",
 
	                                "firstname":   "<firstname>",
 
	                                "lastname" :   "<lastname>",
 
	                                "email" :      "<email>",
 
	                                "active" :     "<bool>",
 
	                                "admin" :      "<bool>",
 
	                                "ldap" :       "<ldap_dn>",
 
	                                "permission" : "repository.(read|write|admin)"
 
	                              },
 
                                  …
 
                                  {
 
                                    "id" :       "<usersgroupid>",
 
                                    "name" :     "<usersgroupname>",
 
                                    "active":    "<bool>",
 
                                    "permission" : "repository.(read|write|admin)"
 
                                  },
 
                                  …
 
                                ]
 
            }
 
    error:  null
 

	
 
get_repo_nodes
 
--------------
 

	
 
returns a list of nodes and it's children in a flat list for a given path 
 
at given revision. It's possible to specify ret_type to show only files or 
 
dirs. This command can be executed only using api_key belonging to user 
 
at given revision. It's possible to specify ret_type to show only `files` or 
 
`dirs`. This command can be executed only using api_key belonging to user 
 
with admin rights
 

	
 
INPUT::
 

	
 
    api_key : "<api_key>"
 
    method :  "get_repo_nodes"
 
    args:     {
 
                "repo_name" : "<name>",
 
                "repo_name" : "<reponame>",
 
                "revision"  : "<revision>",
 
                "root_path" : "<root_path>",
 
                "ret_type"  : "<ret_type>" = 'all'
 
              }
 

	
 
OUTPUT::
 

	
 
    result: [
 
              {
 
                "name" :        "<name>"
 
                "type" :        "<type>",
 
              },
 
              …
 
            ]
 
    error:  null
 

	
 

	
 

	
 
create_repo
 
-----------
 

	
 
Creates a repository. This command can be executed only using api_key
 
belonging to user with admin rights.
 
If repository name contains "/", all needed repository groups will be created.
 
For example "foo/bar/baz" will create groups "foo", "bar" (with "foo" as parent),
 
and create "baz" repository with "bar" as group.
 

	
 
INPUT::
 

	
 
    api_key : "<api_key>"
 
    method :  "create_repo"
 
    args:     {
 
                "name" :        "<name>",
 
                "repo_name" :   "<reponame>",
 
                "owner_name" :  "<ownername>",
 
                "description" : "<description> = ''",
 
                "repo_type" :   "<type> = 'hg'",
 
                "private" :     "<bool> = False"
 
              }
 

	
 
OUTPUT::
 

	
 
    result: None
 
    result: {
 
                "id": "<newrepoid>",
 
                "msg": "Created new repository <reponame>",
 
            }
 
    error:  null
 

	
 
add_user_to_repo
 
----------------
 

	
 
Add a user to a repository. This command can be executed only using api_key
 
belonging to user with admin rights.
 
If "perm" is None, user will be removed from the repository.
 

	
 
INPUT::
 

	
 
    api_key : "<api_key>"
 
    method :  "add_user_to_repo"
 
    args:     {
 
                "repo_name" :  "<reponame>",
 
                "username" :  "<username>",
 
                "username" :   "<username>",
 
                "perm" :       "(None|repository.(read|write|admin))",
 
              }
 

	
 
OUTPUT::
 

	
 
    result: None
 
    result: {
 
                "msg" : "Added perm: <perm> for <username> in repo: <reponame>"
 
            }
 
    error:  null
 

	
 
add_users_group_to_repo
 
-----------------------
 

	
 
Add a users group to a repository. This command can be executed only using 
 
api_key belonging to user with admin rights. If "perm" is None, group will 
 
be removed from the repository.
 

	
 
INPUT::
 

	
 
    api_key : "<api_key>"
 
    method :  "add_users_group_to_repo"
 
    args:     {
 
                "repo_name" :  "<reponame>",
 
                "group_name" :  "<groupname>",
 
                "group_name" : "<groupname>",
 
                "perm" :       "(None|repository.(read|write|admin))",
 
              }
 
\ No newline at end of file
 
              }
 
OUTPUT::
 
    
 
    result: {
 
                "msg" : Added perm: <perm> for <groupname> in repo: <reponame>"
 
            }
 

	
docs/api/index.rst
Show inline comments
 
.. _api:
 
.. _indexapi:
 

	
 
API Reference
 
=============
 

	
 
.. toctree::
 
   :maxdepth: 3
 

	
 
   models
 
   api 
 
\ No newline at end of file
docs/api/models.rst
Show inline comments
 
.. _models:
 

	
 
The :mod:`models` Module
 
========================
 

	
 
.. automodule:: rhodecode.model
 
   :members:
 
   
 
.. automodule:: rhodecode.model.comment
 
   :members:
 
  
 
.. automodule:: rhodecode.model.notification
 
   :members:   
 

	
 
.. automodule:: rhodecode.model.permission
 
   :members:
 
  
 

	
 
.. automodule:: rhodecode.model.repo_permission
 
   :members:      
 

	
 
.. automodule:: rhodecode.model.repo
 
   :members:   
 

	
 
.. automodule:: rhodecode.model.repos_group
 
   :members:
 
   
 
.. automodule:: rhodecode.model.scm
 
   :members:
 

	
 
   
 
.. automodule:: rhodecode.model.user
 
   :members:      
 
   
 
.. automodule:: rhodecode.model.users_group
 
   :members:   
 
\ No newline at end of file
docs/changelog.rst
Show inline comments
 
.. _changelog:
 

	
 
Changelog
 
=========
 

	
 

	
 
1.3.0 (**XXXX-XX-XX**)
 
======================
 

	
 
:status: in-progress
 
:branch: beta
 

	
 
news
 
----
 

	
 
- code review, inspired by github code-comments 
 
- #215 rst and markdown README files support
 
- #252 Container-based and proxy pass-through authentication support
 
- #44 branch browser. Filtering of changelog by branches
 
- mercurial bookmarks support
 
- hover top menu
 
- configurable clone url template with possibility to specify  protocol like 
 
  ssh:// or http:// and also manually alter other parts of clone_url.
 
- enabled largefiles extension by default
 
- optimized summary file pages and saved a lot of unused space in them
 
- #239 option to manually mark repository as fork
 
- #320 mapping of commit authors to RhodeCode users
 
- #304 hashes are displayed using monospace font    
 
- diff configuration, toggle white lines and context lines
 
- #307 configurable diffs, whitespace toggle, increasing context lines
 
- sorting on branches, tags and bookmarks using YUI datatable
 
- improved file filter on files page
 
- implements #330 api method for listing nodes ar particular revision
 
- fixed #331 RhodeCode mangles repository names if the a repository group 
 
  contains the "full path" to the repositories
 
  
 
- #73 added linking issues in commit messages to choosen issue tracker url
 
  based on user defined regular expression
 
    
 
fixes
 
-----
 

	
 
- rewrote dbsession management for atomic operations, and better error handling
 
- fixed sorting of repo tables
 
- #326 escape of special html entities in diffs
 
- normalized user_name => username in api attributes
 
- fixes #298 ldap created users with mixed case emails created conflicts 
 
  on saving a form
 
- fixes issue when owner of a repo couldn't revoke permissions for users 
 
  and groups
 

	
 
1.2.3 (**2011-11-02**)
 
======================
 

	
 
news
 
----
 

	
 
- added option to manage repos group for non admin users
 
- added following API methods for get_users, create_user, get_users_groups, 
 
  get_users_group, create_users_group, add_user_to_users_groups, get_repos, 
 
  get_repo, create_repo, add_user_to_repo
 
- implements #237 added password confirmation for my account 
 
  and admin edit user.
 
- implements #291 email notification for global events are now sent to all
 
  administrator users, and global config email.
 
     
 
fixes
 
-----
 

	
 
- added option for passing auth method for smtp mailer
 
- #276 issue with adding a single user with id>10 to usergroups
 
- #277 fixes windows LDAP settings in which missing values breaks the ldap auth 
 
- #288 fixes managing of repos in a group for non admin user
 

	
 
1.2.2 (**2011-10-17**)
 
======================
 

	
 
news
 
----
 

	
 
- #226 repo groups are available by path instead of numerical id
 
 
 
fixes
 
-----
 

	
 
- #259 Groups with the same name but with different parent group
 
- #260 Put repo in group, then move group to another group -> repo becomes unavailable
 
- #258 RhodeCode 1.2 assumes egg folder is writable (lockfiles problems)
 
- #265 ldap save fails sometimes on converting attributes to booleans, 
 
  added getter and setter into model that will prevent from this on db model level
 
- fixed problems with timestamps issues #251 and #213
 
- fixes #266 RhodeCode allows to create repo with the same name and in 
 
  the same parent as group
 
- fixes #245 Rescan of the repositories on Windows
 
- fixes #248 cannot edit repos inside a group on windows
 
- fixes #219 forking problems on windows
 

	
 
1.2.1 (**2011-10-08**)
 
======================
 

	
 
news
 
----
 

	
 

	
 
fixes
 
-----
 

	
 
- fixed problems with basic auth and push problems 
 
- gui fixes
 
- fixed logger
 

	
 
1.2.0 (**2011-10-07**)
 
======================
 

	
 
news
 
----
 

	
 
- implemented #47 repository groups
 
- implemented #89 Can setup google analytics code from settings menu
 
- implemented #91 added nicer looking archive urls with more download options
 
  like tags, branches
 
- implemented #44 into file browsing, and added follow branch option
 
- implemented #84 downloads can be enabled/disabled for each repository
 
- anonymous repository can be cloned without having to pass default:default
 
  into clone url
 
- fixed #90 whoosh indexer can index chooses repositories passed in command 
 
  line
 
- extended journal with day aggregates and paging
 
- implemented #107 source code lines highlight ranges
 
- implemented #93 customizable changelog on combined revision ranges - 
 
  equivalent of githubs compare view 
 
- implemented #108 extended and more powerful LDAP configuration
 
- implemented #56 users groups
 
- major code rewrites optimized codes for speed and memory usage
 
- raw and diff downloads are now in git format
 
- setup command checks for write access to given path
 
- fixed many issues with international characters and unicode. It uses utf8
 
  decode with replace to provide less errors even with non utf8 encoded strings
 
- #125 added API KEY access to feeds
 
- #109 Repository can be created from external Mercurial link (aka. remote 
 
  repository, and manually updated (via pull) from admin panel
 
- beta git support - push/pull server + basic view for git repos
 
- added followers page and forks page
 
- server side file creation (with binary file upload interface) 
 
  and edition with commits powered by codemirror 
 
- #111 file browser file finder, quick lookup files on whole file tree 
 
- added quick login sliding menu into main page
 
- changelog uses lazy loading of affected files details, in some scenarios 
 
  this can improve speed of changelog page dramatically especially for 
 
  larger repositories.
 
- implements #214 added support for downloading subrepos in download menu.
 
- Added basic API for direct operations on rhodecode via JSON
 
- Implemented advanced hook management
 

	
 
fixes
 
-----
 

	
 
- fixed file browser bug, when switching into given form revision the url was 
 
  not changing
 
- fixed propagation to error controller on simplehg and simplegit middlewares
 
- fixed error when trying to make a download on empty repository
 
- fixed problem with '[' chars in commit messages in journal
 
- fixed #99 Unicode errors, on file node paths with non utf-8 characters
 
- journal fork fixes
 
- removed issue with space inside renamed repository after deletion
 
- fixed strange issue on formencode imports
 
- fixed #126 Deleting repository on Windows, rename used incompatible chars. 
 
- #150 fixes for errors on repositories mapped in db but corrupted in 
 
  filesystem
 
- fixed problem with ascendant characters in realm #181
 
- fixed problem with sqlite file based database connection pool
 
- whoosh indexer and code stats share the same dynamic extensions map
 
- fixes #188 - relationship delete of repo_to_perm entry on user removal
 
- fixes issue #189 Trending source files shows "show more" when no more exist
 
- fixes issue #197 Relative paths for pidlocks
 
- fixes issue #198 password will require only 3 chars now for login form
 
- fixes issue #199 wrong redirection for non admin users after creating a repository
 
- fixes issues #202, bad db constraint made impossible to attach same group 
 
  more than one time. Affects only mysql/postgres
 
- fixes #218 os.kill patch for windows was missing sig param
 
- improved rendering of dag (they are not trimmed anymore when number of 
 
  heads exceeds 5)
 
    
 
1.1.8 (**2011-04-12**)
 
======================
 

	
 
news
 
----
 

	
 
- improved windows support
 

	
 
fixes
 
-----
 

	
 
- fixed #140 freeze of python dateutil library, since new version is python2.x
 
  incompatible
 
- setup-app will check for write permission in given path
 
- cleaned up license info issue #149
 
- fixes for issues #137,#116 and problems with unicode and accented characters.
 
- fixes crashes on gravatar, when passed in email as unicode
 
- fixed tooltip flickering problems
 
- fixed came_from redirection on windows
 
- fixed logging modules, and sql formatters
 
- windows fixes for os.kill issue #133
 
- fixes path splitting for windows issues #148
 
- fixed issue #143 wrong import on migration to 1.1.X
 
- fixed problems with displaying binary files, thanks to Thomas Waldmann
 
- removed name from archive files since it's breaking ui for long repo names
 
- fixed issue with archive headers sent to browser, thanks to Thomas Waldmann
 
- fixed compatibility for 1024px displays, and larger dpi settings, thanks to 
 
  Thomas Waldmann
 
- fixed issue #166 summary pager was skipping 10 revisions on second page
 

	
 

	
 
1.1.7 (**2011-03-23**)
 
======================
 

	
 
news
 
----
 

	
 
fixes
 
-----
 

	
 
- fixed (again) #136 installation support for FreeBSD
 

	
 

	
 
1.1.6 (**2011-03-21**)
 
======================
 

	
 
news
 
----
docs/index.rst
Show inline comments
 
.. _index:
 

	
 
.. include:: ./../README.rst
 

	
 
Documentation
 
-------------
 
Users Guide
 
-----------
 

	
 
**Installation:**
 

	
 
.. toctree::
 
   :maxdepth: 1
 

	
 
   installation
 
   setup
 
   upgrade
 
   
 
**Usage**
 

	
 
.. toctree::
 
   :maxdepth: 1
 

	
 
   usage/general
 
   usage/enable_git
 
   usage/statistics
 
   usage/backup
 
   usage/api_key_access
 
   
 
**Develop**
 

	
 
.. toctree::
 
   :maxdepth: 1
 
   
 
   contributing
 
   changelog
 

	
 
**API**
 

	
 
.. toctree::
 
   :maxdepth: 2
 
   :maxdepth: 1
 

	
 
   api/index
 
   
 

	
 
Other topics
 
------------
 

	
 
* :ref:`genindex`
 
* :ref:`search`
 

	
 
.. _virtualenv: http://pypi.python.org/pypi/virtualenv
 
.. _python: http://www.python.org/
 
.. _django: http://www.djangoproject.com/
 
.. _mercurial: http://mercurial.selenic.com/
 
.. _bitbucket: http://bitbucket.org/
 
.. _subversion: http://subversion.tigris.org/
 
.. _git: http://git-scm.com/
 
.. _celery: http://celeryproject.org/
 
.. _Sphinx: http://sphinx.pocoo.org/
 
.. _vcs: http://pypi.python.org/pypi/vcs
 
\ No newline at end of file
docs/setup.rst
Show inline comments
 
@@ -236,385 +236,403 @@ Certificate Checks : optional
 
    `Enable LDAPS`_ is enabled.  Only DEMAND or HARD offer full SSL security 
 
    while the other options are susceptible to man-in-the-middle attacks.  SSL
 
    certificates can be installed to /etc/openldap/cacerts so that the
 
    DEMAND or HARD options can be used with self-signed certificates or
 
    certificates that do not have traceable certificates of authority.
 

	
 
    NEVER
 
        A serve certificate will never be requested or checked.
 

	
 
    ALLOW
 
        A server certificate is requested.  Failure to provide a
 
        certificate or providing a bad certificate will not terminate the
 
        session.
 

	
 
    TRY
 
        A server certificate is requested.  Failure to provide a
 
        certificate does not halt the session; providing a bad certificate
 
        halts the session.
 

	
 
    DEMAND
 
        A server certificate is requested and must be provided and
 
        authenticated for the session to proceed.
 

	
 
    HARD
 
        The same as DEMAND.
 

	
 
.. _Base DN:
 

	
 
Base DN : required
 
    The Distinguished Name (DN) where searches for users will be performed.
 
    Searches can be controlled by `LDAP Filter`_ and `LDAP Search Scope`_.
 

	
 
.. _LDAP Filter:
 

	
 
LDAP Filter : optional
 
    A LDAP filter defined by RFC 2254.  This is more useful when `LDAP
 
    Search Scope`_ is set to SUBTREE.  The filter is useful for limiting
 
    which LDAP objects are identified as representing Users for
 
    authentication.  The filter is augmented by `Login Attribute`_ below.
 
    This can commonly be left blank.
 

	
 
.. _LDAP Search Scope:
 

	
 
LDAP Search Scope : required
 
    This limits how far LDAP will search for a matching object.
 

	
 
    BASE
 
        Only allows searching of `Base DN`_ and is usually not what you
 
        want.
 

	
 
    ONELEVEL
 
        Searches all entries under `Base DN`_, but not Base DN itself.
 

	
 
    SUBTREE
 
        Searches all entries below `Base DN`_, but not Base DN itself.
 
        When using SUBTREE `LDAP Filter`_ is useful to limit object
 
        location.
 

	
 
.. _Login Attribute:
 

	
 
Login Attribute : required        
 
    The LDAP record attribute that will be matched as the USERNAME or
 
    ACCOUNT used to connect to RhodeCode.  This will be added to `LDAP
 
    Filter`_ for locating the User object.  If `LDAP Filter`_ is specified as
 
    "LDAPFILTER", `Login Attribute`_ is specified as "uid" and the user has
 
    connected as "jsmith" then the `LDAP Filter`_ will be augmented as below
 
    ::
 

	
 
        (&(LDAPFILTER)(uid=jsmith))
 

	
 
.. _ldap_attr_firstname:
 

	
 
First Name Attribute : required
 
    The LDAP record attribute which represents the user's first name.
 

	
 
.. _ldap_attr_lastname:
 

	
 
Last Name Attribute : required
 
    The LDAP record attribute which represents the user's last name.
 

	
 
.. _ldap_attr_email:
 

	
 
Email Attribute : required
 
    The LDAP record attribute which represents the user's email address.
 

	
 
If all data are entered correctly, and python-ldap_ is properly installed
 
users should be granted access to RhodeCode with ldap accounts.  At this
 
time user information is copied from LDAP into the RhodeCode user database.
 
This means that updates of an LDAP user object may not be reflected as a
 
user update in RhodeCode.
 

	
 
If You have problems with LDAP access and believe You entered correct
 
information check out the RhodeCode logs, any error messages sent from LDAP
 
will be saved there.
 

	
 
Active Directory
 
''''''''''''''''
 

	
 
RhodeCode can use Microsoft Active Directory for user authentication.  This
 
is done through an LDAP or LDAPS connection to Active Directory.  The
 
following LDAP configuration settings are typical for using Active
 
Directory ::
 

	
 
 Base DN              = OU=SBSUsers,OU=Users,OU=MyBusiness,DC=v3sys,DC=local
 
 Login Attribute      = sAMAccountName
 
 First Name Attribute = givenName
 
 Last Name Attribute  = sn
 
 E-mail Attribute     = mail
 

	
 
All other LDAP settings will likely be site-specific and should be
 
appropriately configured.
 

	
 

	
 

	
 
Authentication by container or reverse-proxy
 
--------------------------------------------
 

	
 
Starting with version 1.3, RhodeCode supports delegating the authentication
 
of users to its WSGI container, or to a reverse-proxy server through which all
 
clients access the application.
 

	
 
When these authentication methods are enabled in RhodeCode, it uses the
 
username that the container/proxy (Apache/Nginx/etc) authenticated and doesn't
 
perform the authentication itself. The authorization, however, is still done by
 
RhodeCode according to its settings.
 

	
 
When a user logs in for the first time using these authentication methods,
 
a matching user account is created in RhodeCode with default permissions. An
 
administrator can then modify it using RhodeCode's admin interface.
 
It's also possible for an administrator to create accounts and configure their
 
permissions before the user logs in for the first time.
 

	
 
Container-based authentication
 
''''''''''''''''''''''''''''''
 

	
 
In a container-based authentication setup, RhodeCode reads the user name from
 
the ``REMOTE_USER`` server variable provided by the WSGI container.
 

	
 
After setting up your container (see `Apache's WSGI config`_), you'd need
 
to configure it to require authentication on the location configured for
 
RhodeCode.
 

	
 
In order for RhodeCode to start using the provided username, you should set the
 
following in the [app:main] section of your .ini file::
 

	
 
    container_auth_enabled = true
 

	
 

	
 
Proxy pass-through authentication
 
'''''''''''''''''''''''''''''''''
 

	
 
In a proxy pass-through authentication setup, RhodeCode reads the user name
 
from the ``X-Forwarded-User`` request header, which should be configured to be
 
sent by the reverse-proxy server.
 

	
 
After setting up your proxy solution (see `Apache virtual host reverse proxy example`_,
 
`Apache as subdirectory`_ or `Nginx virtual host example`_), you'd need to
 
configure the authentication and add the username in a request header named
 
``X-Forwarded-User``.
 

	
 
For example, the following config section for Apache sets a subdirectory in a
 
reverse-proxy setup with basic auth::
 

	
 
    <Location /<someprefix> >
 
      ProxyPass http://127.0.0.1:5000/<someprefix>
 
      ProxyPassReverse http://127.0.0.1:5000/<someprefix>
 
      SetEnvIf X-Url-Scheme https HTTPS=1
 

	
 
      AuthType Basic
 
      AuthName "RhodeCode authentication"
 
      AuthUserFile /home/web/rhodecode/.htpasswd
 
      require valid-user
 

	
 
      RequestHeader unset X-Forwarded-User
 

	
 
      RewriteEngine On
 
      RewriteCond %{LA-U:REMOTE_USER} (.+)
 
      RewriteRule .* - [E=RU:%1]
 
      RequestHeader set X-Forwarded-User %{RU}e
 
    </Location> 
 

	
 
In order for RhodeCode to start using the forwarded username, you should set
 
the following in the [app:main] section of your .ini file::
 

	
 
    proxypass_auth_enabled = true
 

	
 
.. note::
 
   If you enable proxy pass-through authentication, make sure your server is
 
   only accessible through the proxy. Otherwise, any client would be able to
 
   forge the authentication header and could effectively become authenticated
 
   using any account of their liking.
 

	
 
Integration with Issue trackers
 
-------------------------------
 

	
 
RhodeCode provides a simple integration with issue trackers. It's possible
 
to define a regular expression that will fetch issue id stored in commit
 
messages and replace that with an url to this issue. To enable this simply
 
uncomment following variables in the ini file::
 

	
 
    url_pat = (?:^#|\s#)(\w+)
 
    issue_server = https://myissueserver.com/issue/{id}
 
    issue_prefix = #
 

	
 
`url_pat` is the regular expression that will match issues, default given regex
 
will match issues in format of #<number> eg. #300. 
 
Matched issues will be replace with the `issue_server` url replacing {id} with
 
id fetched from regex. Since the # is striped `issue_prefix` is added as a 
 
prefix to url. `issue_prefix` can be something different than # if you pass 
 
ISSUE- as issue prefix this will generate an url in format 
 
`<a href="https://myissueserver.com/issue/300">ISSUE-300</a>`  
 

	
 
Hook management
 
---------------
 

	
 
Hooks can be managed in similar way to this used in .hgrc files.
 
To access hooks setting click `advanced setup` on Hooks section of Mercurial
 
Settings in Admin. 
 

	
 
There are 4 built in hooks that cannot be changed (only enable/disable by
 
checkboxes on previos section).
 
To add another custom hook simply fill in first section with 
 
<name>.<hook_type> and the second one with hook path. Example hooks
 
can be found at *rhodecode.lib.hooks*. 
 

	
 

	
 
Setting Up Celery
 
-----------------
 

	
 
Since version 1.1 celery is configured by the rhodecode ini configuration files.
 
Simply set use_celery=true in the ini file then add / change the configuration 
 
variables inside the ini file.
 

	
 
Remember that the ini files use the format with '.' not with '_' like celery.
 
So for example setting `BROKER_HOST` in celery means setting `broker.host` in
 
the config file.
 

	
 
In order to start using celery run::
 

	
 
 paster celeryd <configfile.ini>
 

	
 

	
 
.. note::
 
   Make sure you run this command from the same virtualenv, and with the same 
 
   user that rhodecode runs.
 
   
 
HTTPS support
 
-------------
 

	
 
There are two ways to enable https:
 

	
 
- Set HTTP_X_URL_SCHEME in your http server headers, than rhodecode will
 
  recognize this headers and make proper https redirections
 
- Alternatively, change the `force_https = true` flag in the ini configuration 
 
  to force using https, no headers are needed than to enable https
 

	
 

	
 
Nginx virtual host example
 
--------------------------
 

	
 
Sample config for nginx using proxy::
 

	
 
    upstream rc {
 
        server 127.0.0.1:5000;
 
        # add more instances for load balancing
 
        #server 127.0.0.1:5001;
 
        #server 127.0.0.1:5002;
 
    }
 
    
 
    server {
 
       listen          80;
 
       server_name     hg.myserver.com;
 
       access_log      /var/log/nginx/rhodecode.access.log;
 
       error_log       /var/log/nginx/rhodecode.error.log;
 

	
 
       location / {
 
            try_files $uri @rhode;
 
       }
 
    
 
       location @rhode {
 
            proxy_pass      http://rc;
 
            include         /etc/nginx/proxy.conf;
 
       }
 

	
 
    }  
 
  
 
Here's the proxy.conf. It's tuned so it will not timeout on long
 
pushes or large pushes::
 
    
 
    proxy_redirect              off;
 
    proxy_set_header            Host $host;
 
    proxy_set_header            X-Url-Scheme $scheme;
 
    proxy_set_header            X-Host $http_host;
 
    proxy_set_header            X-Real-IP $remote_addr;
 
    proxy_set_header            X-Forwarded-For $proxy_add_x_forwarded_for;
 
    proxy_set_header            Proxy-host $proxy_host;
 
    client_max_body_size        400m;
 
    client_body_buffer_size     128k;
 
    proxy_buffering             off;
 
    proxy_connect_timeout       7200;
 
    proxy_send_timeout          7200;
 
    proxy_read_timeout          7200;
 
    proxy_buffers               8 32k;
 
 
 
Also, when using root path with nginx you might set the static files to false
 
in the production.ini file::
 

	
 
    [app:main]
 
      use = egg:rhodecode
 
      full_stack = true
 
      static_files = false
 
      lang=en
 
      cache_dir = %(here)s/data
 

	
 
In order to not have the statics served by the application. This improves speed.
 

	
 

	
 
Apache virtual host reverse proxy example
 
-----------------------------------------
 

	
 
Here is a sample configuration file for apache using proxy::
 

	
 
    <VirtualHost *:80>
 
            ServerName hg.myserver.com
 
            ServerAlias hg.myserver.com
 
    
 
            <Proxy *>
 
              Order allow,deny
 
              Allow from all
 
            </Proxy>
 
    
 
            #important !
 
            #Directive to properly generate url (clone url) for pylons
 
            ProxyPreserveHost On
 
    
 
            #rhodecode instance
 
            ProxyPass / http://127.0.0.1:5000/
 
            ProxyPassReverse / http://127.0.0.1:5000/
 
            
 
            #to enable https use line below
 
            #SetEnvIf X-Url-Scheme https HTTPS=1
 
            
 
    </VirtualHost> 
 

	
 

	
 
Additional tutorial
 
http://wiki.pylonshq.com/display/pylonscookbook/Apache+as+a+reverse+proxy+for+Pylons
 

	
 

	
 
Apache as subdirectory
 
----------------------
 

	
 
Apache subdirectory part::
 

	
 
    <Location /<someprefix> >
 
      ProxyPass http://127.0.0.1:5000/<someprefix>
 
      ProxyPassReverse http://127.0.0.1:5000/<someprefix>
 
      SetEnvIf X-Url-Scheme https HTTPS=1
 
    </Location> 
 

	
 
Besides the regular apache setup you will need to add the following line
 
into [app:main] section of your .ini file::
 

	
 
    filter-with = proxy-prefix
 

	
 
Add the following at the end of the .ini file::
 

	
 
    [filter:proxy-prefix]
 
    use = egg:PasteDeploy#prefix
 
    prefix = /<someprefix> 
 

	
 

	
 
then change <someprefix> into your choosen prefix
 

	
 
Apache's WSGI config
 
--------------------
 

	
 
Alternatively, RhodeCode can be set up with Apache under mod_wsgi. For
 
that, you'll need to:
 

	
 
- Install mod_wsgi. If using a Debian-based distro, you can install
 
  the package libapache2-mod-wsgi::
 

	
 
    aptitude install libapache2-mod-wsgi
 

	
 
- Enable mod_wsgi::
 

	
 
    a2enmod wsgi
 

	
 
- Create a wsgi dispatch script, like the one below. Make sure you
 
  check the paths correctly point to where you installed RhodeCode
 
  and its Python Virtual Environment.
 
- Enable the WSGIScriptAlias directive for the wsgi dispatch script,
 
  as in the following example. Once again, check the paths are
 
  correctly specified.
 

	
 
Here is a sample excerpt from an Apache Virtual Host configuration file::
 

	
 
    WSGIDaemonProcess pylons user=www-data group=www-data processes=1 \
 
        threads=4 \
 
        python-path=/home/web/rhodecode/pyenv/lib/python2.6/site-packages
 
    WSGIScriptAlias / /home/web/rhodecode/dispatch.wsgi
 

	
docs/usage/api_key_access.rst
Show inline comments
 
deleted file
production.ini
Show inline comments
 
################################################################################
 
################################################################################
 
# RhodeCode - Pylons environment configuration                                 #
 
#                                                                              # 
 
# The %(here)s variable will be replaced with the parent directory of this file#
 
################################################################################
 

	
 
[DEFAULT]
 
debug = true
 
pdebug = false
 
################################################################################
 
## Uncomment and replace with the address which should receive                ## 
 
## any error reports after application crash                                  ##
 
## Additionally those settings will be used by RhodeCode mailing system       ##
 
################################################################################
 
#email_to = admin@localhost
 
#error_email_from = paste_error@localhost
 
#app_email_from = rhodecode-noreply@localhost
 
#error_message =
 
#email_prefix = [RhodeCode]
 

	
 
#smtp_server = mail.server.com
 
#smtp_username = 
 
#smtp_password = 
 
#smtp_port = 
 
#smtp_use_tls = false
 
#smtp_use_ssl = true
 
# Specify available auth parameters here (e.g. LOGIN PLAIN CRAM-MD5, etc.)
 
#smtp_auth = 
 

	
 
[server:main]
 
##nr of threads to spawn
 
threadpool_workers = 5
 

	
 
##max request before thread respawn
 
threadpool_max_requests = 10
 

	
 
##option to use threads of process
 
use_threadpool = true
 

	
 
use = egg:Paste#http
 
host = 127.0.0.1
 
port = 8001
 

	
 
[app:main]
 
use = egg:rhodecode
 
full_stack = true
 
static_files = true
 
lang=en
 
cache_dir = %(here)s/data
 
index_dir = %(here)s/data/index
 
app_instance_uuid = prod1234
 
cut_off_limit = 256000
 
force_https = false 
 
commit_parse_limit = 50
 
use_gravatar = true
 
container_auth_enabled = false
 
proxypass_auth_enabled = false
 

	
 
## overwrite schema of clone url
 
# available vars:
 
# scheme - http/https
 
# user - current user
 
# pass - password 
 
# netloc - network location
 
# path - usually repo_name
 
# clone_uri = {scheme}://{user}{pass}{netloc}{path}
 
## available vars:
 
## scheme - http/https
 
## user - current user
 
## pass - password 
 
## netloc - network location
 
## path - usually repo_name
 

	
 
#clone_uri = {scheme}://{user}{pass}{netloc}{path}
 

	
 
## issue tracking mapping for commits messages
 
## uncomment url_pat, issue_server, issue_prefix to enable
 

	
 

	
 
## pattern to get the issues from commit messages
 
## default one used here is #1234
 

	
 
#url_pat = (?:^#|\s#)(\w+)
 

	
 
## server url to the issue, each {id} will be replaced with id
 
## fetched from the regex
 

	
 
#issue_server = https://myissueserver.com/issue/{id}
 

	
 
## prefix to add to link to indicate it's an url
 
## #314 will be replaced by <issue_prefix><id>
 

	
 
#issue_prefix = #
 

	
 

	
 
####################################
 
###        CELERY CONFIG        ####
 
####################################
 
use_celery = false
 
broker.host = localhost
 
broker.vhost = rabbitmqhost
 
broker.port = 5672
 
broker.user = rabbitmq
 
broker.password = qweqwe
 

	
 
celery.imports = rhodecode.lib.celerylib.tasks
 

	
 
celery.result.backend = amqp
 
celery.result.dburi = amqp://
 
celery.result.serialier = json
 

	
 
#celery.send.task.error.emails = true
 
#celery.amqp.task.result.expires = 18000
 

	
 
celeryd.concurrency = 2
 
#celeryd.log.file = celeryd.log
 
celeryd.log.level = debug
 
celeryd.max.tasks.per.child = 1
 

	
 
#tasks will never be sent to the queue, but executed locally instead.
 
celery.always.eager = false
 

	
 
####################################
 
###         BEAKER CACHE        ####
 
####################################
 
beaker.cache.data_dir=%(here)s/data/cache/data
 
beaker.cache.lock_dir=%(here)s/data/cache/lock
 

	
 
beaker.cache.regions=super_short_term,short_term,long_term,sql_cache_short,sql_cache_med,sql_cache_long
 

	
 
beaker.cache.super_short_term.type=memory
 
beaker.cache.super_short_term.expire=10
 
beaker.cache.super_short_term.key_length = 256
 

	
 
beaker.cache.short_term.type=memory
 
beaker.cache.short_term.expire=60
 
beaker.cache.short_term.key_length = 256
 

	
 
beaker.cache.long_term.type=memory
 
beaker.cache.long_term.expire=36000
 
beaker.cache.long_term.key_length = 256
 

	
 
beaker.cache.sql_cache_short.type=memory
 
beaker.cache.sql_cache_short.expire=10
 
beaker.cache.sql_cache_short.key_length = 256
 

	
 
beaker.cache.sql_cache_med.type=memory
 
beaker.cache.sql_cache_med.expire=360
 
beaker.cache.sql_cache_med.key_length = 256
 

	
 
beaker.cache.sql_cache_long.type=file
 
beaker.cache.sql_cache_long.expire=3600
 
beaker.cache.sql_cache_long.key_length = 256
 

	
 
####################################
 
###       BEAKER SESSION        ####
 
####################################
 
## Type of storage used for the session, current types are 
 
## dbm, file, memcached, database, and memory. 
 
## The storage uses the Container API 
 
## that is also used by the cache system.
 

	
 
## db session example
 

	
 
#beaker.session.type = ext:database
 
#beaker.session.sa.url = postgresql://postgres:qwe@localhost/rhodecode
 
#beaker.session.table_name = db_session 
 

	
 
## encrypted cookie session, good for many instances
 
#beaker.session.type = cookie
 

	
 
beaker.session.type = file
 
beaker.session.key = rhodecode
 
#beaker.session.encrypt_key = g654dcno0-9873jhgfreyu
 
#beaker.session.validate_key = 9712sds2212c--zxc123
 
beaker.session.timeout = 36000
 
beaker.session.httponly = true
 

	
 
## uncomment for https secure cookie
 
beaker.session.secure = false
 

	
 
##auto save the session to not to use .save()
 
beaker.session.auto = False
 

	
 
##true exire at browser close
 
#beaker.session.cookie_expires = 3600
 

	
 

	
 
################################################################################
 
## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT*  ##
 
## Debug mode will enable the interactive debugging tool, allowing ANYONE to  ##
 
## execute malicious code after an exception is raised.                       ##
 
################################################################################
 
set debug = false
 

	
 
##################################
 
###       LOGVIEW CONFIG       ###
 
##################################
 
logview.sqlalchemy = #faa
 
logview.pylons.templating = #bfb
 
logview.pylons.util = #eee
 

	
 
#########################################################
 
### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG    ###
 
#########################################################
 
#sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db
 
sqlalchemy.db1.url = postgresql://postgres:qwe@localhost/rhodecode
 
sqlalchemy.db1.echo = false
 
sqlalchemy.db1.pool_recycle = 3600
 
sqlalchemy.convert_unicode = true
 

	
 
################################
 
### LOGGING CONFIGURATION   ####
 
################################
 
[loggers]
 
keys = root, routes, rhodecode, sqlalchemy, beaker, templates
 

	
 
[handlers]
 
keys = console, console_sql
 

	
 
[formatters]
 
keys = generic, color_formatter, color_formatter_sql
 

	
 
#############
 
## LOGGERS ##
 
#############
 
[logger_root]
 
level = NOTSET
 
handlers = console
 

	
 
[logger_routes]
 
level = DEBUG
 
handlers = 
 
qualname = routes.middleware
 
# "level = DEBUG" logs the route matched and routing variables.
 
propagate = 1
 

	
 
[logger_beaker]
 
level = DEBUG
 
handlers = 
 
qualname = beaker.container
 
propagate = 1
 

	
 
[logger_templates]
 
level = INFO
 
handlers = 
 
qualname = pylons.templating
 
propagate = 1
 

	
 
[logger_rhodecode]
 
level = DEBUG
 
handlers = 
 
qualname = rhodecode
 
propagate = 1
 

	
 
[logger_sqlalchemy]
 
level = INFO
 
handlers = console_sql
 
qualname = sqlalchemy.engine
 
propagate = 0
 

	
 
##############
 
## HANDLERS ##
 
##############
 

	
 
[handler_console]
 
class = StreamHandler
 
args = (sys.stderr,)
 
level = INFO
 
formatter = generic
 

	
 
[handler_console_sql]
 
class = StreamHandler
 
args = (sys.stderr,)
 
level = WARN
 
formatter = generic
 

	
 
################
 
## FORMATTERS ##
 
################
 

	
 
[formatter_generic]
 
format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
 
datefmt = %Y-%m-%d %H:%M:%S
 

	
 
[formatter_color_formatter]
rhodecode/config/deployment.ini_tmpl
Show inline comments
 
################################################################################
 
################################################################################
 
# RhodeCode - Pylons environment configuration                                 #
 
#                                                                              # 
 
# The %(here)s variable will be replaced with the parent directory of this file#
 
################################################################################
 

	
 
[DEFAULT]
 
debug = true
 
pdebug = false
 
################################################################################
 
## Uncomment and replace with the address which should receive                ## 
 
## any error reports after application crash                                  ##
 
## Additionally those settings will be used by RhodeCode mailing system       ##
 
################################################################################
 
#email_to = admin@localhost
 
#error_email_from = paste_error@localhost
 
#app_email_from = rhodecode-noreply@localhost
 
#error_message =
 
#email_prefix = [RhodeCode]
 

	
 
#smtp_server = mail.server.com
 
#smtp_username = 
 
#smtp_password = 
 
#smtp_port = 
 
#smtp_use_tls = false
 
#smtp_use_ssl = true
 
# Specify available auth parameters here (e.g. LOGIN PLAIN CRAM-MD5, etc.)
 
#smtp_auth = 
 

	
 
[server:main]
 
##nr of threads to spawn
 
threadpool_workers = 5
 

	
 
##max request before thread respawn
 
threadpool_max_requests = 10
 

	
 
##option to use threads of process
 
use_threadpool = true
 

	
 
use = egg:Paste#http
 
host = 127.0.0.1
 
port = 5000
 

	
 
[app:main]
 
use = egg:rhodecode
 
full_stack = true
 
static_files = true
 
lang=en
 
cache_dir = %(here)s/data
 
index_dir = %(here)s/data/index
 
app_instance_uuid = ${app_instance_uuid}
 
cut_off_limit = 256000
 
force_https = false 
 
commit_parse_limit = 50
 
use_gravatar = true
 
container_auth_enabled = false
 
proxypass_auth_enabled = false
 

	
 
## overwrite schema of clone url
 
# available vars:
 
# scheme - http/https
 
# user - current user
 
# pass - password 
 
# netloc - network location
 
# path - usually repo_name
 
## available vars:
 
## scheme - http/https
 
## user - current user
 
## pass - password 
 
## netloc - network location
 
## path - usually repo_name
 

	
 
# clone_uri = {scheme}://{user}{pass}{netloc}{path}
 

	
 
## issue tracking mapping for commits messages
 
## uncomment url_pat, issue_server, issue_prefix to enable
 

	
 

	
 
## pattern to get the issues from commit messages
 
## default one used here is #1234
 

	
 
#url_pat = (?:^#|\s#)(\w+)
 

	
 
## server url to the issue, each {id} will be replaced with id
 
## fetched from the regex
 

	
 
#issue_server = https://myissueserver.com/issue/{id}
 

	
 
## prefix to add to link to indicate it's an url
 
## #314 will be replaced by <issue_prefix><id>
 

	
 
#issue_prefix = #
 

	
 

	
 
####################################
 
###        CELERY CONFIG        ####
 
####################################
 
use_celery = false
 
broker.host = localhost
 
broker.vhost = rabbitmqhost
 
broker.port = 5672
 
broker.user = rabbitmq
 
broker.password = qweqwe
 

	
 
celery.imports = rhodecode.lib.celerylib.tasks
 

	
 
celery.result.backend = amqp
 
celery.result.dburi = amqp://
 
celery.result.serialier = json
 

	
 
#celery.send.task.error.emails = true
 
#celery.amqp.task.result.expires = 18000
 

	
 
celeryd.concurrency = 2
 
#celeryd.log.file = celeryd.log
 
celeryd.log.level = debug
 
celeryd.max.tasks.per.child = 1
 

	
 
#tasks will never be sent to the queue, but executed locally instead.
 
celery.always.eager = false
 

	
 
####################################
 
###         BEAKER CACHE        ####
 
####################################
 
beaker.cache.data_dir=%(here)s/data/cache/data
 
beaker.cache.lock_dir=%(here)s/data/cache/lock
 

	
 
beaker.cache.regions=super_short_term,short_term,long_term,sql_cache_short,sql_cache_med,sql_cache_long
 

	
 
beaker.cache.super_short_term.type=memory
 
beaker.cache.super_short_term.expire=10
 
beaker.cache.super_short_term.key_length = 256
 

	
 
beaker.cache.short_term.type=memory
 
beaker.cache.short_term.expire=60
 
beaker.cache.short_term.key_length = 256
 

	
 
beaker.cache.long_term.type=memory
 
beaker.cache.long_term.expire=36000
 
beaker.cache.long_term.key_length = 256
 

	
 
beaker.cache.sql_cache_short.type=memory
 
beaker.cache.sql_cache_short.expire=10
 
beaker.cache.sql_cache_short.key_length = 256
 

	
 
beaker.cache.sql_cache_med.type=memory
 
beaker.cache.sql_cache_med.expire=360
 
beaker.cache.sql_cache_med.key_length = 256
 

	
 
beaker.cache.sql_cache_long.type=file
 
beaker.cache.sql_cache_long.expire=3600
 
beaker.cache.sql_cache_long.key_length = 256
 

	
 
####################################
 
###       BEAKER SESSION        ####
 
####################################
 
## Type of storage used for the session, current types are 
 
## dbm, file, memcached, database, and memory. 
 
## The storage uses the Container API 
 
## that is also used by the cache system.
 

	
 
## db session example
 

	
 
#beaker.session.type = ext:database
 
#beaker.session.sa.url = postgresql://postgres:qwe@localhost/rhodecode
 
#beaker.session.table_name = db_session 
 

	
 
## encrypted cookie session, good for many instances
 
#beaker.session.type = cookie
 

	
 
beaker.session.type = file
 
beaker.session.key = rhodecode
 
beaker.session.encrypt_key = ${app_instance_secret}
 
beaker.session.validate_key = ${app_instance_secret}
 
beaker.session.timeout = 36000
 
beaker.session.httponly = true
 

	
 
## uncomment for https secure cookie
 
beaker.session.secure = false
 

	
 
##auto save the session to not to use .save()
 
beaker.session.auto = False
 

	
 
##true exire at browser close
 
#beaker.session.cookie_expires = 3600
 

	
 

	
 
################################################################################
 
## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT*  ##
 
## Debug mode will enable the interactive debugging tool, allowing ANYONE to  ##
 
## execute malicious code after an exception is raised.                       ##
 
################################################################################
 
set debug = false
 

	
 
##################################
 
###       LOGVIEW CONFIG       ###
 
##################################
 
logview.sqlalchemy = #faa
 
logview.pylons.templating = #bfb
 
logview.pylons.util = #eee
 

	
 
#########################################################
 
### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG    ###
 
#########################################################
 

	
 
# SQLITE [default]
 
sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db
 
 
 
# POSTGRESQL
 
# sqlalchemy.db1.url = postgresql://user:pass@localhost/rhodecode
 

	
 
# MySQL
 
# sqlalchemy.db1.url = mysql://user:pass@localhost/rhodecode
 

	
 
# see sqlalchemy docs for others
 

	
 
sqlalchemy.db1.echo = false
 
sqlalchemy.db1.pool_recycle = 3600
 
sqlalchemy.convert_unicode = true
 

	
 
################################
 
### LOGGING CONFIGURATION   ####
 
################################
 
[loggers]
 
keys = root, routes, rhodecode, sqlalchemy, beaker, templates
 

	
 
[handlers]
 
keys = console, console_sql
 

	
 
[formatters]
 
keys = generic, color_formatter, color_formatter_sql
 

	
 
#############
 
## LOGGERS ##
 
#############
 
[logger_root]
 
level = NOTSET
 
handlers = console
 

	
 
[logger_routes]
 
level = DEBUG
 
handlers = 
 
qualname = routes.middleware
 
# "level = DEBUG" logs the route matched and routing variables.
 
propagate = 1
 

	
 
[logger_beaker]
 
level = DEBUG
 
handlers = 
 
qualname = beaker.container
 
propagate = 1
 

	
 
[logger_templates]
 
level = INFO
 
handlers = 
 
qualname = pylons.templating
 
propagate = 1
 

	
 
[logger_rhodecode]
 
level = DEBUG
 
handlers = 
 
qualname = rhodecode
 
propagate = 1
 

	
 
[logger_sqlalchemy]
 
level = INFO
 
handlers = console_sql
 
qualname = sqlalchemy.engine
 
propagate = 0
 

	
 
##############
 
## HANDLERS ##
 
##############
 

	
 
[handler_console]
 
class = StreamHandler
 
args = (sys.stderr,)
 
level = INFO
 
formatter = color_formatter
 

	
 
[handler_console_sql]
 
class = StreamHandler
 
args = (sys.stderr,)
 
level = WARN
 
formatter = color_formatter_sql
 

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

	
 
    API controller for RhodeCode
 

	
 
    :created_on: Aug 20, 2011
 
    :author: marcink
 
    :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
 
    :license: GPLv3, see COPYING for more details.
 
"""
 
# This program is free software; you can redistribute it and/or
 
# modify it under the terms of the GNU General Public License
 
# as published by the Free Software Foundation; version 2
 
# of the License or (at your opinion) any later version of the license.
 
#
 
# This program is distributed in the hope that it will be useful,
 
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
# GNU General Public License for more details.
 
#
 
# You should have received a copy of the GNU General Public License
 
# along with this program; if not, write to the Free Software
 
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 
# MA  02110-1301, USA.
 

	
 
import traceback
 
import logging
 

	
 
from sqlalchemy.orm.exc import NoResultFound
 

	
 
from rhodecode.controllers.api import JSONRPCController, JSONRPCError
 
from rhodecode.lib.auth import HasPermissionAllDecorator, \
 
    HasPermissionAnyDecorator
 

	
 
from rhodecode.model.meta import Session
 
from rhodecode.model.scm import ScmModel
 
from rhodecode.model.db import User, UsersGroup, RepoGroup, Repository
 
from rhodecode.model.repo import RepoModel
 
from rhodecode.model.user import UserModel
 
from rhodecode.model.repo_permission import RepositoryPermissionModel
 
from rhodecode.model.users_group import UsersGroupModel
 
from rhodecode.model.repos_group import ReposGroupModel
 

	
 

	
 
log = logging.getLogger(__name__)
 

	
 

	
 
class ApiController(JSONRPCController):
 
    """
 
    API Controller
 

	
 

	
 
    Each method needs to have USER as argument this is then based on given
 
    API_KEY propagated as instance of user object
 

	
 
    Preferably this should be first argument also
 

	
 

	
 
    Each function should also **raise** JSONRPCError for any
 
    errors that happens
 

	
 
    """
 

	
 
    @HasPermissionAllDecorator('hg.admin')
 
    def pull(self, apiuser, repo):
 
    def pull(self, apiuser, repo_name):
 
        """
 
        Dispatch pull action on given repo
 

	
 

	
 
        :param user:
 
        :param repo:
 
        :param repo_name:
 
        """
 

	
 
        if Repository.is_valid(repo) is False:
 
            raise JSONRPCError('Unknown repo "%s"' % repo)
 
        if Repository.is_valid(repo_name) is False:
 
            raise JSONRPCError('Unknown repo "%s"' % repo_name)
 

	
 
        try:
 
            ScmModel().pull_changes(repo, self.rhodecode_user.username)
 
            return 'Pulled from %s' % repo
 
            ScmModel().pull_changes(repo_name, self.rhodecode_user.username)
 
            return 'Pulled from %s' % repo_name
 
        except Exception:
 
            raise JSONRPCError('Unable to pull changes from "%s"' % repo)
 
            raise JSONRPCError('Unable to pull changes from "%s"' % repo_name)
 

	
 
    @HasPermissionAllDecorator('hg.admin')
 
    def get_user(self, apiuser, username):
 
        """"
 
        Get a user by username
 

	
 
        :param apiuser:
 
        :param username:
 
        """
 

	
 
        user = User.get_by_username(username)
 
        if not user:
 
            return None
 

	
 
        return dict(
 
            id=user.user_id,
 
            username=user.username,
 
            firstname=user.name,
 
            lastname=user.lastname,
 
            email=user.email,
 
            active=user.active,
 
            admin=user.admin,
 
            ldap=user.ldap_dn
 
        )
 

	
 
    @HasPermissionAllDecorator('hg.admin')
 
    def get_users(self, apiuser):
 
        """"
 
        Get all users
 

	
 
        :param apiuser:
 
        """
 

	
 
        result = []
 
        for user in User.getAll():
 
            result.append(
 
                dict(
 
                    id=user.user_id,
 
                    username=user.username,
 
                    firstname=user.name,
 
                    lastname=user.lastname,
 
                    email=user.email,
 
                    active=user.active,
 
                    admin=user.admin,
 
                    ldap=user.ldap_dn
 
                )
 
            )
 
        return result
 

	
 
    @HasPermissionAllDecorator('hg.admin')
 
    def create_user(self, apiuser, username, password, firstname,
 
                    lastname, email, active=True, admin=False, ldap_dn=None):
 
        """
 
        Create new user
 

	
 
        :param apiuser:
 
        :param username:
 
        :param password:
 
        :param name:
 
        :param lastname:
 
        :param email:
 
        :param active:
 
        :param admin:
 
        :param ldap_dn:
 
        """
 

	
 
        if User.get_by_username(username):
 
            raise JSONRPCError("user %s already exist" % username)
 

	
 
        try:
 
            UserModel().create_or_update(username, password, email, firstname,
 
                                         lastname, active, admin, ldap_dn)
 
            usr = UserModel().create_or_update(
 
                username, password, email, firstname,
 
                lastname, active, admin, ldap_dn
 
            )
 
            Session.commit()
 
            return dict(msg='created new user %s' % username)
 
            return dict(
 
                id=usr.user_id,
 
                msg='created new user %s' % username
 
            )
 
        except Exception:
 
            log.error(traceback.format_exc())
 
            raise JSONRPCError('failed to create user %s' % username)
 

	
 
    @HasPermissionAllDecorator('hg.admin')
 
    def get_users_group(self, apiuser, group_name):
 
        """"
 
        Get users group by name
 

	
 
        :param apiuser:
 
        :param group_name:
 
        """
 

	
 
        users_group = UsersGroup.get_by_group_name(group_name)
 
        if not users_group:
 
            return None
 

	
 
        members = []
 
        for user in users_group.members:
 
            user = user.user
 
            members.append(dict(id=user.user_id,
 
                            username=user.username,
 
                            firstname=user.name,
 
                            lastname=user.lastname,
 
                            email=user.email,
 
                            active=user.active,
 
                            admin=user.admin,
 
                            ldap=user.ldap_dn))
 

	
 
        return dict(id=users_group.users_group_id,
 
                    name=users_group.users_group_name,
 
                    group_name=users_group.users_group_name,
 
                    active=users_group.users_group_active,
 
                    members=members)
 

	
 
    @HasPermissionAllDecorator('hg.admin')
 
    def get_users_groups(self, apiuser):
 
        """"
 
        Get all users groups
 

	
 
        :param apiuser:
 
        """
 

	
 
        result = []
 
        for users_group in UsersGroup.getAll():
 
            members = []
 
            for user in users_group.members:
 
                user = user.user
 
                members.append(dict(id=user.user_id,
 
                                username=user.username,
 
                                firstname=user.name,
 
                                lastname=user.lastname,
 
                                email=user.email,
 
                                active=user.active,
 
                                admin=user.admin,
 
                                ldap=user.ldap_dn))
 

	
 
            result.append(dict(id=users_group.users_group_id,
 
                                name=users_group.users_group_name,
 
                                group_name=users_group.users_group_name,
 
                                active=users_group.users_group_active,
 
                                members=members))
 
        return result
 

	
 
    @HasPermissionAllDecorator('hg.admin')
 
    def create_users_group(self, apiuser, name, active=True):
 
    def create_users_group(self, apiuser, group_name, active=True):
 
        """
 
        Creates an new usergroup
 

	
 
        :param name:
 
        :param group_name:
 
        :param active:
 
        """
 

	
 
        if self.get_users_group(apiuser, name):
 
            raise JSONRPCError("users group %s already exist" % name)
 
        if self.get_users_group(apiuser, group_name):
 
            raise JSONRPCError("users group %s already exist" % group_name)
 

	
 
        try:
 
            ug = UsersGroupModel().create(name=name, active=active)
 
            ug = UsersGroupModel().create(name=group_name, active=active)
 
            Session.commit()
 
            return dict(id=ug.users_group_id,
 
                        msg='created new users group %s' % name)
 
                        msg='created new users group %s' % group_name)
 
        except Exception:
 
            log.error(traceback.format_exc())
 
            raise JSONRPCError('failed to create group %s' % name)
 
            raise JSONRPCError('failed to create group %s' % group_name)
 

	
 
    @HasPermissionAllDecorator('hg.admin')
 
    def add_user_to_users_group(self, apiuser, group_name, username):
 
        """"
 
        Add a user to a group
 

	
 
        :param apiuser:
 
        :param group_name:
 
        :param username:
 
        """
 

	
 
        try:
 
            users_group = UsersGroup.get_by_group_name(group_name)
 
            if not users_group:
 
                raise JSONRPCError('unknown users group %s' % group_name)
 

	
 
            try:
 
                user = User.get_by_username(username)
 
            except NoResultFound:
 
                raise JSONRPCError('unknown user %s' % username)
 

	
 
            ugm = UsersGroupModel().add_user_to_group(users_group, user)
 
            Session.commit()
 
            return dict(id=ugm.users_group_member_id,
 
                        msg='created new users group member')
 
        except Exception:
 
            log.error(traceback.format_exc())
 
            raise JSONRPCError('failed to create users group member')
 

	
 
    @HasPermissionAnyDecorator('hg.admin')
 
    def get_repo(self, apiuser, repo_name):
 
        """"
 
        Get repository by name
 

	
 
        :param apiuser:
 
        :param repo_name:
 
        """
 

	
 
        repo = Repository.get_by_repo_name(repo_name)
 
        if repo is None:
 
            raise JSONRPCError('unknown repository %s' % repo)
 

	
 
        members = []
 
        for user in repo.repo_to_perm:
 
            perm = user.permission.permission_name
 
            user = user.user
 
            members.append(
 
                dict(
 
                    type_="user",
 
                    id=user.user_id,
 
                    username=user.username,
 
                    firstname=user.name,
 
                    lastname=user.lastname,
 
                    email=user.email,
 
                    active=user.active,
 
                    admin=user.admin,
 
                    ldap=user.ldap_dn,
 
                    permission=perm
 
                )
 
            )
 
        for users_group in repo.users_group_to_perm:
 
            perm = users_group.permission.permission_name
 
            users_group = users_group.users_group
 
            members.append(
 
                dict(
 
                    type_="users_group",
 
                    id=users_group.users_group_id,
 
                    name=users_group.users_group_name,
 
                    active=users_group.users_group_active,
 
                    permission=perm
 
                )
 
            )
 

	
 
        return dict(
 
            id=repo.repo_id,
 
            name=repo.repo_name,
 
            repo_name=repo.repo_name,
 
            type=repo.repo_type,
 
            description=repo.description,
 
            members=members
 
        )
 

	
 
    @HasPermissionAnyDecorator('hg.admin')
 
    def get_repos(self, apiuser):
 
        """"
 
        Get all repositories
 

	
 
        :param apiuser:
 
        """
 

	
 
        result = []
 
        for repository in Repository.getAll():
 
            result.append(
 
                dict(
 
                    id=repository.repo_id,
 
                    name=repository.repo_name,
 
                    repo_name=repository.repo_name,
 
                    type=repository.repo_type,
 
                    description=repository.description
 
                )
 
            )
 
        return result
 

	
 
    @HasPermissionAnyDecorator('hg.admin')
 
    def get_repo_nodes(self, apiuser, repo_name, revision, root_path,
 
                       ret_type='all'):
 
        """
 
        returns a list of nodes and it's children
 
        for a given path at given revision. It's possible to specify ret_type
 
        to show only files or dirs
 

	
 
        :param apiuser:
 
        :param repo_name: name of repository
 
        :param revision: revision for which listing should be done
 
        :param root_path: path from which start displaying
 
        :param ret_type: return type 'all|files|dirs' nodes
 
        """
 
        try:
 
            _d, _f = ScmModel().get_nodes(repo_name, revision, root_path,
 
                                          flat=False)
 
            _map = {
 
                'all': _d + _f,
 
                'files': _f,
 
                'dirs': _d,
 
            }
 
            return _map[ret_type]
 
        except KeyError:
 
            raise JSONRPCError('ret_type must be one of %s' % _map.keys())
 
        except Exception, e:
 
            raise JSONRPCError(e)
 

	
 
    @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
 
    def create_repo(self, apiuser, name, owner_name, description='',
 
    def create_repo(self, apiuser, repo_name, owner_name, description='',
 
                    repo_type='hg', private=False):
 
        """
 
        Create a repository
 

	
 
        :param apiuser:
 
        :param name:
 
        :param repo_name:
 
        :param description:
 
        :param type:
 
        :param private:
 
        :param owner_name:
 
        """
 

	
 
        try:
 
            try:
 
                owner = User.get_by_username(owner_name)
 
            except NoResultFound:
 
                raise JSONRPCError('unknown user %s' % owner)
 

	
 
            if self.get_repo(apiuser, name):
 
                raise JSONRPCError("repo %s already exist" % name)
 
            if Repository.get_by_repo_name(repo_name):
 
                raise JSONRPCError("repo %s already exist" % repo_name)
 

	
 
            groups = name.split('/')
 
            groups = repo_name.split('/')
 
            real_name = groups[-1]
 
            groups = groups[:-1]
 
            parent_id = None
 
            for g in groups:
 
                group = RepoGroup.get_by_group_name(g)
 
                if not group:
 
                    group = ReposGroupModel().create(
 
                        dict(
 
                            group_name=g,
 
                            group_description='',
 
                            group_parent_id=parent_id
 
                        )
 
                    )
 
                parent_id = group.group_id
 

	
 
            RepoModel().create(
 
            repo = RepoModel().create(
 
                dict(
 
                    repo_name=real_name,
 
                    repo_name_full=name,
 
                    repo_name_full=repo_name,
 
                    description=description,
 
                    private=private,
 
                    repo_type=repo_type,
 
                    repo_group=parent_id,
 
                    clone_uri=None
 
                ),
 
                owner
 
            )
 
            Session.commit()
 

	
 
            return dict(
 
                id=repo.repo_id,
 
                msg="Created new repository %s" % repo.repo_name
 
            )
 

	
 
        except Exception:
 
            log.error(traceback.format_exc())
 
            raise JSONRPCError('failed to create repository %s' % name)
 
            raise JSONRPCError('failed to create repository %s' % repo_name)
 

	
 
    @HasPermissionAnyDecorator('hg.admin')
 
    def add_user_to_repo(self, apiuser, repo_name, username, perm):
 
        """
 
        Add permission for a user to a repository
 

	
 
        :param apiuser:
 
        :param repo_name:
 
        :param username:
 
        :param perm:
 
        """
 

	
 
        try:
 
            repo = Repository.get_by_repo_name(repo_name)
 
            if repo is None:
 
                raise JSONRPCError('unknown repository %s' % repo)
 

	
 
            try:
 
                user = User.get_by_username(username)
 
            except NoResultFound:
 
                raise JSONRPCError('unknown user %s' % user)
 

	
 
            RepositoryPermissionModel()\
 
                .update_or_delete_user_permission(repo, user, perm)
 
            Session.commit()
 

	
 
            return dict(
 
                msg='Added perm: %s for %s in repo: %s' % (
 
                    perm, username, repo_name
 
                )
 
            )
 
        except Exception:
 
            log.error(traceback.format_exc())
 
            raise JSONRPCError(
 
                'failed to edit permission %(repo)s for %(user)s' % dict(
 
                    user=username, repo=repo_name
 
                )
 
            )
 

	
 
    @HasPermissionAnyDecorator('hg.admin')
 
    def add_users_group_to_repo(self, apiuser, repo_name, group_name, perm):
 
        """
 
        Add permission for a users group to a repository
 

	
 
        :param apiuser:
 
        :param repo_name:
 
        :param group_name:
 
        :param perm:
 
        """
 

	
 
        try:
 
            repo = Repository.get_by_repo_name(repo_name)
 
            if repo is None:
 
                raise JSONRPCError('unknown repository %s' % repo)
 

	
 
            try:
 
                user_group = UsersGroup.get_by_group_name(group_name)
 
            except NoResultFound:
 
                raise JSONRPCError('unknown users group %s' % user_group)
 

	
 
            RepositoryPermissionModel()\
 
                .update_or_delete_users_group_permission(repo, user_group,
 
                                                         perm)
 
            Session.commit()
 
            return dict(
 
                msg='Added perm: %s for %s in repo: %s' % (
 
                    perm, group_name, repo_name
 
                )
 
            )
 
        except Exception:
 
            log.error(traceback.format_exc())
 
            raise JSONRPCError(
 
                'failed to edit permission %(repo)s for %(usergr)s' % dict(
 
                    usergr=group_name, repo=repo_name
 
                )
 
            )
rhodecode/lib/db_manage.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
"""
 
    rhodecode.lib.db_manage
 
    ~~~~~~~~~~~~~~~~~~~~~~~
 

	
 
    Database creation, and setup module for RhodeCode. Used for creation
 
    of database as well as for migration operations
 

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

	
 
import os
 
import sys
 
import uuid
 
import logging
 
from os.path import dirname as dn, join as jn
 

	
 
from rhodecode import __dbversion__
 
from rhodecode.model import meta
 

	
 
from rhodecode.model.user import UserModel
 
from rhodecode.lib.utils import ask_ok
 
from rhodecode.model import init_model
 
from rhodecode.model.db import User, Permission, RhodeCodeUi, \
 
    RhodeCodeSetting, UserToPerm, DbMigrateVersion
 

	
 
from sqlalchemy.engine import create_engine
 

	
 
log = logging.getLogger(__name__)
 

	
 

	
 
class DbManage(object):
 
    def __init__(self, log_sql, dbconf, root, tests=False):
 
        self.dbname = dbconf.split('/')[-1]
 
        self.tests = tests
 
        self.root = root
 
        self.dburi = dbconf
 
        self.log_sql = log_sql
 
        self.db_exists = False
 
        self.init_db()
 

	
 
    def init_db(self):
 
        engine = create_engine(self.dburi, echo=self.log_sql)
 
        init_model(engine)
 
        self.sa = meta.Session
 

	
 
    def create_tables(self, override=False):
 
        """
 
        Create a auth database
 
        """
 

	
 
        log.info("Any existing database is going to be destroyed")
 
        if self.tests:
 
            destroy = True
 
        else:
 
            destroy = ask_ok('Are you sure to destroy old database ? [y/n]')
 
        if not destroy:
 
            sys.exit()
 
        if destroy:
 
            meta.Base.metadata.drop_all()
 

	
 
        checkfirst = not override
 
        meta.Base.metadata.create_all(checkfirst=checkfirst)
 
        log.info('Created tables for %s', self.dbname)
 

	
 
    def set_db_version(self):
 
        ver = DbMigrateVersion()
 
        ver.version = __dbversion__
 
        ver.repository_id = 'rhodecode_db_migrations'
 
        ver.repository_path = 'versions'
 
        self.sa.add(ver)
 
        log.info('db version set to: %s', __dbversion__)
 

	
 
    def upgrade(self):
 
        """
 
        Upgrades given database schema to given revision following
 
        all needed steps, to perform the upgrade
 

	
 
        """
 

	
 
        from rhodecode.lib.dbmigrate.migrate.versioning import api
 
        from rhodecode.lib.dbmigrate.migrate.exceptions import \
 
            DatabaseNotControlledError
 

	
 
        if 'sqlite' in self.dburi:
 
            print (
 
               '********************** WARNING **********************\n'
 
               'Make sure your version of sqlite is at least 3.7.X.  \n'
 
               'Earlier versions are known to fail on some migrations\n'
 
               '*****************************************************\n'
 
            )
 
        upgrade = ask_ok('You are about to perform database upgrade, make '
 
                         'sure You backed up your database before. '
 
                         'Continue ? [y/n]')
 
        if not upgrade:
 
            sys.exit('Nothing done')
 

	
 
        repository_path = jn(dn(dn(dn(os.path.realpath(__file__)))),
 
                             'rhodecode/lib/dbmigrate')
 
        db_uri = self.dburi
 

	
 
        try:
 
            curr_version = api.db_version(db_uri, repository_path)
 
            msg = ('Found current database under version'
 
                 ' control with version %s' % curr_version)
 

	
 
        except (RuntimeError, DatabaseNotControlledError):
 
            curr_version = 1
 
            msg = ('Current database is not under version control. Setting'
 
                   ' as version %s' % curr_version)
 
            api.version_control(db_uri, repository_path, curr_version)
 

	
 
        print (msg)
 

	
 
        if curr_version == __dbversion__:
 
            sys.exit('This database is already at the newest version')
 

	
 
        #======================================================================
 
        # UPGRADE STEPS
 
        #======================================================================
 
        class UpgradeSteps(object):
 
            """
 
            Those steps follow schema versions so for example schema
 
            for example schema with seq 002 == step_2 and so on.
 
            """
 

	
 
            def __init__(self, klass):
 
                self.klass = klass
 

	
 
            def step_0(self):
 
                # step 0 is the schema upgrade, and than follow proper upgrades
 
                print ('attempting to do database upgrade to version %s' \
 
                                % __dbversion__)
 
                api.upgrade(db_uri, repository_path, __dbversion__)
 
                print ('Schema upgrade completed')
 

	
 
            def step_1(self):
 
                pass
 

	
 
            def step_2(self):
 
                print ('Patching repo paths for newer version of RhodeCode')
 
                self.klass.fix_repo_paths()
 

	
 
                print ('Patching default user of RhodeCode')
 
                self.klass.fix_default_user()
 

	
 
                log.info('Changing ui settings')
 
                self.klass.create_ui_settings()
 

	
 
            def step_3(self):
 
                print ('Adding additional settings into RhodeCode db')
 
                self.klass.fix_settings()
 
                print ('Adding ldap defaults')
 
                self.klass.create_ldap_options(skip_existing=True)
 

	
 
            def step_4(self):
 
                print ('TODO:')
 

	
 
        upgrade_steps = [0] + range(curr_version + 1, __dbversion__ + 1)
 

	
 
        # CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE
 
        for step in upgrade_steps:
 
            print ('performing upgrade step %s' % step)
 
            getattr(UpgradeSteps(self), 'step_%s' % step)()
 

	
 
    def fix_repo_paths(self):
 
        """
 
        Fixes a old rhodecode version path into new one without a '*'
 
        """
 

	
 
        paths = self.sa.query(RhodeCodeUi)\
 
                .filter(RhodeCodeUi.ui_key == '/')\
 
                .scalar()
 

	
 
        paths.ui_value = paths.ui_value.replace('*', '')
 

	
 
        try:
 
            self.sa.add(paths)
 
            self.sa.commit()
 
        except:
 
            self.sa.rollback()
 
            raise
 

	
 
    def fix_default_user(self):
 
        """
 
        Fixes a old default user with some 'nicer' default values,
 
        used mostly for anonymous access
 
        """
 
        def_user = self.sa.query(User)\
 
                .filter(User.username == 'default')\
 
                .one()
 

	
 
        def_user.name = 'Anonymous'
 
        def_user.lastname = 'User'
 
        def_user.email = 'anonymous@rhodecode.org'
 

	
 
        try:
 
            self.sa.add(def_user)
 
            self.sa.commit()
 
        except:
 
            self.sa.rollback()
 
            raise
 

	
 
    def fix_settings(self):
 
        """
 
        Fixes rhodecode settings adds ga_code key for google analytics
 
        """
 

	
 
        hgsettings3 = RhodeCodeSetting('ga_code', '')
 

	
 
        try:
 
            self.sa.add(hgsettings3)
 
            self.sa.commit()
 
        except:
 
            self.sa.rollback()
 
            raise
 

	
 
    def admin_prompt(self, second=False):
 
        if not self.tests:
 
            import getpass
 

	
 
            def get_password():
 
                password = getpass.getpass('Specify admin password '
 
                                           '(min 6 chars):')
 
                confirm = getpass.getpass('Confirm password:')
 

	
 
                if password != confirm:
 
                    log.error('passwords mismatch')
 
                    return False
 
                if len(password) < 6:
 
                    log.error('password is to short use at least 6 characters')
 
                    return False
 

	
 
                return password
 

	
 
            username = raw_input('Specify admin username:')
 

	
 
            password = get_password()
 
            if not password:
 
                #second try
 
                password = get_password()
 
                if not password:
 
                    sys.exit()
 

	
 
            email = raw_input('Specify admin email:')
 
            self.create_user(username, password, email, True)
 
        else:
 
            log.info('creating admin and regular test users')
 
            from rhodecode.tests import TEST_USER_ADMIN_LOGIN,\
 
            TEST_USER_ADMIN_PASS, TEST_USER_ADMIN_EMAIL,\
 
            TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,\
 
            TEST_USER_REGULAR_EMAIL, TEST_USER_REGULAR2_LOGIN, \
 
            TEST_USER_REGULAR2_PASS, TEST_USER_REGULAR2_EMAIL
 

	
 
            self.create_user(TEST_USER_ADMIN_LOGIN, TEST_USER_ADMIN_PASS,
 
                             TEST_USER_ADMIN_EMAIL, True)
 

	
 
            self.create_user(TEST_USER_REGULAR_LOGIN, TEST_USER_REGULAR_PASS,
 
                             TEST_USER_REGULAR_EMAIL, False)
 

	
 
            self.create_user(TEST_USER_REGULAR2_LOGIN, TEST_USER_REGULAR2_PASS,
 
                             TEST_USER_REGULAR2_EMAIL, False)
 

	
 
    def create_ui_settings(self):
 
        """
 
        Creates ui settings, fills out hooks
 
        and disables dotencode
 
        """
 

	
 
        #HOOKS
 
        hooks1_key = RhodeCodeUi.HOOK_UPDATE
 
        hooks1_ = self.sa.query(RhodeCodeUi)\
 
            .filter(RhodeCodeUi.ui_key == hooks1_key).scalar()
 

	
 
        hooks1 = RhodeCodeUi() if hooks1_ is None else hooks1_
 
        hooks1.ui_section = 'hooks'
 
        hooks1.ui_key = hooks1_key
 
        hooks1.ui_value = 'hg update >&2'
 
        hooks1.ui_active = False
 

	
 
        hooks2_key = RhodeCodeUi.HOOK_REPO_SIZE
 
        hooks2_ = self.sa.query(RhodeCodeUi)\
 
            .filter(RhodeCodeUi.ui_key == hooks2_key).scalar()
 

	
 
        hooks2 = RhodeCodeUi() if hooks2_ is None else hooks2_
 
        hooks2.ui_section = 'hooks'
 
        hooks2.ui_key = hooks2_key
 
        hooks2.ui_value = 'python:rhodecode.lib.hooks.repo_size'
 

	
 
        hooks3 = RhodeCodeUi()
 
        hooks3.ui_section = 'hooks'
 
        hooks3.ui_key = RhodeCodeUi.HOOK_PUSH
 
        hooks3.ui_value = 'python:rhodecode.lib.hooks.log_push_action'
 

	
 
        hooks4 = RhodeCodeUi()
 
        hooks4.ui_section = 'hooks'
 
        hooks4.ui_key = RhodeCodeUi.HOOK_PULL
 
        hooks4.ui_value = 'python:rhodecode.lib.hooks.log_pull_action'
 

	
 
        # For mercurial 1.7 set backward comapatibility with format
 
        dotencode_disable = RhodeCodeUi()
 
        dotencode_disable.ui_section = 'format'
 
        dotencode_disable.ui_key = 'dotencode'
 
        dotencode_disable.ui_value = 'false'
 

	
 
        # enable largefiles
 
        largefiles = RhodeCodeUi()
 
        largefiles.ui_section = 'extensions'
 
        largefiles.ui_key = 'largefiles'
 
        largefiles.ui_value = ''
 

	
 
        self.sa.add(hooks1)
 
        self.sa.add(hooks2)
 
        self.sa.add(hooks3)
 
        self.sa.add(hooks4)
 
        self.sa.add(largefiles)
 

	
 
    def create_ldap_options(self, skip_existing=False):
 
        """Creates ldap settings"""
 

	
 
        for k, v in [('ldap_active', 'false'), ('ldap_host', ''),
 
                    ('ldap_port', '389'), ('ldap_tls_kind', 'PLAIN'),
 
                    ('ldap_tls_reqcert', ''), ('ldap_dn_user', ''),
 
                    ('ldap_dn_pass', ''), ('ldap_base_dn', ''),
 
                    ('ldap_filter', ''), ('ldap_search_scope', ''),
 
                    ('ldap_attr_login', ''), ('ldap_attr_firstname', ''),
 
                    ('ldap_attr_lastname', ''), ('ldap_attr_email', '')]:
 

	
 
            if skip_existing and RhodeCodeSetting.get_by_name(k) != None:
 
                log.debug('Skipping option %s' % k)
 
                continue
 
            setting = RhodeCodeSetting(k, v)
 
            self.sa.add(setting)
 

	
 
    def config_prompt(self, test_repo_path='', retries=3):
 
        if retries == 3:
 
            log.info('Setting up repositories config')
 

	
 
        if not self.tests and not test_repo_path:
 
            path = raw_input('Specify valid full path to your repositories'
 
                        ' you can change this later in application settings:')
 
        else:
 
            path = test_repo_path
 
        path_ok = True
 

	
 
        # check proper dir
 
        if not os.path.isdir(path):
 
            path_ok = False
 
            log.error('Given path %s is not a valid directory', path)
 

	
rhodecode/lib/helpers.py
Show inline comments
 
"""Helper functions
 

	
 
Consists of functions to typically be used within templates, but also
 
available to Controllers. This module is available to both as 'h'.
 
"""
 
import random
 
import hashlib
 
import StringIO
 
import urllib
 
import math
 
import logging
 

	
 
from datetime import datetime
 
from pygments.formatters.html import HtmlFormatter
 
from pygments import highlight as code_highlight
 
from pylons import url, request, config
 
from pylons.i18n.translation import _, ungettext
 
from hashlib import md5
 

	
 
from webhelpers.html import literal, HTML, escape
 
from webhelpers.html.tools import *
 
from webhelpers.html.builder import make_tag
 
from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
 
    end_form, file, form, hidden, image, javascript_link, link_to, \
 
    link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
 
    submit, text, password, textarea, title, ul, xml_declaration, radio
 
from webhelpers.html.tools import auto_link, button_to, highlight, \
 
    js_obfuscate, mail_to, strip_links, strip_tags, tag_re
 
from webhelpers.number import format_byte_size, format_bit_size
 
from webhelpers.pylonslib import Flash as _Flash
 
from webhelpers.pylonslib.secure_form import secure_form
 
from webhelpers.text import chop_at, collapse, convert_accented_entities, \
 
    convert_misc_entities, lchop, plural, rchop, remove_formatting, \
 
    replace_whitespace, urlify, truncate, wrap_paragraphs
 
from webhelpers.date import time_ago_in_words
 
from webhelpers.paginate import Page
 
from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
 
    convert_boolean_attrs, NotGiven, _make_safe_id_component
 

	
 
from rhodecode.lib.annotate import annotate_highlight
 
from rhodecode.lib.utils import repo_name_slug
 
from rhodecode.lib import str2bool, safe_unicode, safe_str, get_changeset_safe
 
from rhodecode.lib.markup_renderer import MarkupRenderer
 

	
 
log = logging.getLogger(__name__)
 

	
 

	
 
def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
 
    """
 
    Reset button
 
    """
 
    _set_input_attrs(attrs, type, name, value)
 
    _set_id_attr(attrs, id, name)
 
    convert_boolean_attrs(attrs, ["disabled"])
 
    return HTML.input(**attrs)
 

	
 
reset = _reset
 
safeid = _make_safe_id_component
 

	
 

	
 
def FID(raw_id, path):
 
    """
 
    Creates a uniqe ID for filenode based on it's hash of path and revision
 
    it's safe to use in urls
 

	
 
    :param raw_id:
 
    :param path:
 
    """
 

	
 
    return 'C-%s-%s' % (short_id(raw_id), md5(path).hexdigest()[:12])
 

	
 

	
 
def get_token():
 
    """Return the current authentication token, creating one if one doesn't
 
    already exist.
 
    """
 
    token_key = "_authentication_token"
 
    from pylons import session
 
    if not token_key in session:
 
        try:
 
            token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
 
        except AttributeError: # Python < 2.4
 
            token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
 
        session[token_key] = token
 
        if hasattr(session, 'save'):
 
            session.save()
 
    return session[token_key]
 

	
 
class _GetError(object):
 
    """Get error from form_errors, and represent it as span wrapped error
 
    message
 

	
 
    :param field_name: field to fetch errors for
 
    :param form_errors: form errors dict
 
    """
 

	
 
    def __call__(self, field_name, form_errors):
 
        tmpl = """<span class="error_msg">%s</span>"""
 
        if form_errors and form_errors.has_key(field_name):
 
            return literal(tmpl % form_errors.get(field_name))
 

	
 
get_error = _GetError()
 

	
 
class _ToolTip(object):
 

	
 
    def __call__(self, tooltip_title, trim_at=50):
 
        """Special function just to wrap our text into nice formatted
 
        autowrapped text
 

	
 
        :param tooltip_title:
 
        """
 
        return escape(tooltip_title)
 
tooltip = _ToolTip()
 

	
 
class _FilesBreadCrumbs(object):
 

	
 
    def __call__(self, repo_name, rev, paths):
 
        if isinstance(paths, str):
 
            paths = safe_unicode(paths)
 
        url_l = [link_to(repo_name, url('files_home',
 
                                        repo_name=repo_name,
 
                                        revision=rev, f_path=''))]
 
        paths_l = paths.split('/')
 
        for cnt, p in enumerate(paths_l):
 
            if p != '':
 
                url_l.append(link_to(p,
 
                                     url('files_home',
 
                                         repo_name=repo_name,
 
                                         revision=rev,
 
                                         f_path='/'.join(paths_l[:cnt + 1])
 
                                         )
 
                                     )
 
                             )
 

	
 
        return literal('/'.join(url_l))
 

	
 
files_breadcrumbs = _FilesBreadCrumbs()
 

	
 
class CodeHtmlFormatter(HtmlFormatter):
 
    """My code Html Formatter for source codes
 
    """
 

	
 
    def wrap(self, source, outfile):
 
        return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
 

	
 
    def _wrap_code(self, source):
 
        for cnt, it in enumerate(source):
 
            i, t = it
 
            t = '<div id="L%s">%s</div>' % (cnt + 1, t)
 
            yield i, t
 

	
 
    def _wrap_tablelinenos(self, inner):
 
        dummyoutfile = StringIO.StringIO()
 
        lncount = 0
 
        for t, line in inner:
 
            if t:
 
                lncount += 1
 
            dummyoutfile.write(line)
 

	
 
        fl = self.linenostart
 
        mw = len(str(lncount + fl - 1))
 
        sp = self.linenospecial
 
        st = self.linenostep
 
        la = self.lineanchors
 
        aln = self.anchorlinenos
 
        nocls = self.noclasses
 
        if sp:
 
            lines = []
 

	
 
            for i in range(fl, fl + lncount):
 
                if i % st == 0:
 
                    if i % sp == 0:
 
                        if aln:
 
                            lines.append('<a href="#%s%d" class="special">%*d</a>' %
 
                                         (la, i, mw, i))
 
                        else:
 
                            lines.append('<span class="special">%*d</span>' % (mw, i))
 
                    else:
 
                        if aln:
 
                            lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
 
                        else:
 
                            lines.append('%*d' % (mw, i))
 
                else:
 
                    lines.append('')
 
            ls = '\n'.join(lines)
 
        else:
 
            lines = []
 
            for i in range(fl, fl + lncount):
 
                if i % st == 0:
 
                    if aln:
 
                        lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
 
                    else:
 
                        lines.append('%*d' % (mw, i))
 
                else:
 
                    lines.append('')
 
            ls = '\n'.join(lines)
 

	
 
        # in case you wonder about the seemingly redundant <div> here: since the
 
        # content in the other cell also is wrapped in a div, some browsers in
 
        # some configurations seem to mess up the formatting...
 
        if nocls:
 
            yield 0, ('<table class="%stable">' % self.cssclass +
 
                      '<tr><td><div class="linenodiv" '
 
                      'style="background-color: #f0f0f0; padding-right: 10px">'
 
                      '<pre style="line-height: 125%">' +
 
                      ls + '</pre></div></td><td id="hlcode" class="code">')
 
        else:
 
            yield 0, ('<table class="%stable">' % self.cssclass +
 
                      '<tr><td class="linenos"><div class="linenodiv"><pre>' +
 
                      ls + '</pre></div></td><td id="hlcode" class="code">')
 
        yield 0, dummyoutfile.getvalue()
 
        yield 0, '</td></tr></table>'
 

	
 

	
 
def pygmentize(filenode, **kwargs):
 
    """pygmentize function using pygments
 

	
 
    :param filenode:
 
    """
 

	
 
    return literal(code_highlight(filenode.content,
 
                                  filenode.lexer, CodeHtmlFormatter(**kwargs)))
 

	
 

	
 
def pygmentize_annotation(repo_name, filenode, **kwargs):
 
    """
 
    pygmentize function for annotation
 

	
 
    :param filenode:
 
    """
 

	
 
    color_dict = {}
 

	
 
    def gen_color(n=10000):
 
        """generator for getting n of evenly distributed colors using
 
        hsv color and golden ratio. It always return same order of colors
 

	
 
        :returns: RGB tuple
 
@@ -539,217 +542,251 @@ def gravatar_url(email_address, size=30)
 
        email_address = safe_str(email_address)
 
    # construct the url
 
    gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
 
    gravatar_url += urllib.urlencode({'d':default, 's':str(size)})
 

	
 
    return gravatar_url
 

	
 

	
 
#==============================================================================
 
# REPO PAGER, PAGER FOR REPOSITORY
 
#==============================================================================
 
class RepoPage(Page):
 

	
 
    def __init__(self, collection, page=1, items_per_page=20,
 
                 item_count=None, url=None, **kwargs):
 

	
 
        """Create a "RepoPage" instance. special pager for paging
 
        repository
 
        """
 
        self._url_generator = url
 

	
 
        # Safe the kwargs class-wide so they can be used in the pager() method
 
        self.kwargs = kwargs
 

	
 
        # Save a reference to the collection
 
        self.original_collection = collection
 

	
 
        self.collection = collection
 

	
 
        # The self.page is the number of the current page.
 
        # The first page has the number 1!
 
        try:
 
            self.page = int(page) # make it int() if we get it as a string
 
        except (ValueError, TypeError):
 
            self.page = 1
 

	
 
        self.items_per_page = items_per_page
 

	
 
        # Unless the user tells us how many items the collections has
 
        # we calculate that ourselves.
 
        if item_count is not None:
 
            self.item_count = item_count
 
        else:
 
            self.item_count = len(self.collection)
 

	
 
        # Compute the number of the first and last available page
 
        if self.item_count > 0:
 
            self.first_page = 1
 
            self.page_count = int(math.ceil(float(self.item_count) /
 
                                            self.items_per_page))
 
            self.last_page = self.first_page + self.page_count - 1
 

	
 
            # Make sure that the requested page number is the range of
 
            # valid pages
 
            if self.page > self.last_page:
 
                self.page = self.last_page
 
            elif self.page < self.first_page:
 
                self.page = self.first_page
 

	
 
            # Note: the number of items on this page can be less than
 
            #       items_per_page if the last page is not full
 
            self.first_item = max(0, (self.item_count) - (self.page *
 
                                                          items_per_page))
 
            self.last_item = ((self.item_count - 1) - items_per_page *
 
                              (self.page - 1))
 

	
 
            self.items = list(self.collection[self.first_item:self.last_item + 1])
 

	
 

	
 
            # Links to previous and next page
 
            if self.page > self.first_page:
 
                self.previous_page = self.page - 1
 
            else:
 
                self.previous_page = None
 

	
 
            if self.page < self.last_page:
 
                self.next_page = self.page + 1
 
            else:
 
                self.next_page = None
 

	
 
        # No items available
 
        else:
 
            self.first_page = None
 
            self.page_count = 0
 
            self.last_page = None
 
            self.first_item = None
 
            self.last_item = None
 
            self.previous_page = None
 
            self.next_page = None
 
            self.items = []
 

	
 
        # This is a subclass of the 'list' type. Initialise the list now.
 
        list.__init__(self, reversed(self.items))
 

	
 

	
 
def changed_tooltip(nodes):
 
    """
 
    Generates a html string for changed nodes in changeset page.
 
    It limits the output to 30 entries
 

	
 
    :param nodes: LazyNodesGenerator
 
    """
 
    if nodes:
 
        pref = ': <br/> '
 
        suf = ''
 
        if len(nodes) > 30:
 
            suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
 
        return literal(pref + '<br/> '.join([safe_unicode(x.path)
 
                                             for x in nodes[:30]]) + suf)
 
    else:
 
        return ': ' + _('No Files')
 

	
 

	
 

	
 
def repo_link(groups_and_repos):
 
    """
 
    Makes a breadcrumbs link to repo within a group
 
    joins &raquo; on each group to create a fancy link
 

	
 
    ex::
 
        group >> subgroup >> repo
 

	
 
    :param groups_and_repos:
 
    """
 
    groups, repo_name = groups_and_repos
 

	
 
    if not groups:
 
        return repo_name
 
    else:
 
        def make_link(group):
 
            return link_to(group.name, url('repos_group_home',
 
                                           group_name=group.group_name))
 
        return literal(' &raquo; '.join(map(make_link, groups)) + \
 
                       " &raquo; " + repo_name)
 

	
 
def fancy_file_stats(stats):
 
    """
 
    Displays a fancy two colored bar for number of added/deleted
 
    lines of code on file
 

	
 
    :param stats: two element list of added/deleted lines of code
 
    """
 

	
 
    a, d, t = stats[0], stats[1], stats[0] + stats[1]
 
    width = 100
 
    unit = float(width) / (t or 1)
 

	
 
    # needs > 9% of width to be visible or 0 to be hidden
 
    a_p = max(9, unit * a) if a > 0 else 0
 
    d_p = max(9, unit * d) if d > 0 else 0
 
    p_sum = a_p + d_p
 

	
 
    if p_sum > width:
 
        #adjust the percentage to be == 100% since we adjusted to 9
 
        if a_p > d_p:
 
            a_p = a_p - (p_sum - width)
 
        else:
 
            d_p = d_p - (p_sum - width)
 

	
 
    a_v = a if a > 0 else ''
 
    d_v = d if d > 0 else ''
 

	
 

	
 
    def cgen(l_type):
 
        mapping = {'tr':'top-right-rounded-corner',
 
                   'tl':'top-left-rounded-corner',
 
                   'br':'bottom-right-rounded-corner',
 
                   'bl':'bottom-left-rounded-corner'}
 
        map_getter = lambda x:mapping[x]
 

	
 
        if l_type == 'a' and d_v:
 
            #case when added and deleted are present
 
            return ' '.join(map(map_getter, ['tl', 'bl']))
 

	
 
        if l_type == 'a' and not d_v:
 
            return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
 

	
 
        if l_type == 'd' and a_v:
 
            return ' '.join(map(map_getter, ['tr', 'br']))
 

	
 
        if l_type == 'd' and not a_v:
 
            return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
 

	
 

	
 

	
 
    d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (cgen('a'),
 
                                                                 a_p, a_v)
 
    d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (cgen('d'),
 
                                                                   d_p, d_v)
 
    return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
 

	
 

	
 
def urlify_text(text):
 
def urlify_text(text_):
 
    import re
 

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

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

	
 
    return literal(url_pat.sub(url_func, text))
 
    return literal(url_pat.sub(url_func, text_))
 

	
 
def urlify_commit(text_):
 
    import re
 
    import traceback
 
    
 
    try:
 
        conf = config['app_conf']
 
        
 
        URL_PAT = re.compile(r'%s' % conf.get('url_pat'))
 
        
 
        if URL_PAT:
 
            ISSUE_SERVER = conf.get('issue_server')
 
            ISSUE_PREFIX = conf.get('issue_prefix')
 
            def url_func(match_obj):
 
                issue_id = match_obj.groups()[0]
 
                tmpl = (
 
                '<a class="%(cls)s" href="%(url)s">'
 
                ' %(issue-prefix)s%(id-repr)s'
 
                '</a>'
 
                )
 
                return tmpl % (
 
                    {
 
                     'cls':'issue-tracker-link',
 
                     'url':ISSUE_SERVER.replace('{id}',issue_id),
 
                     'id-repr':issue_id,
 
                     'issue-prefix':ISSUE_PREFIX,
 
                     'serv':ISSUE_SERVER,
 
                    }
 
                )
 
            return literal(URL_PAT.sub(url_func, text_))
 
    except:
 
        log.error(traceback.format_exc())
 
        pass
 

	
 
    return text_
 

	
 
def rst(source):
 
    return literal('<div class="rst-block">%s</div>' %
 
                   MarkupRenderer.rst(source))
 

	
 
def rst_w_mentions(source):
 
    """
 
    Wrapped rst renderer with @mention highlighting
 

	
 
    :param source:
 
    """
 
    return literal('<div class="rst-block">%s</div>' %
 
                   MarkupRenderer.rst_with_mentions(source))
rhodecode/lib/rcmail/exceptions.py
Show inline comments
 

	
 

	
 
class InvalidMessage(RuntimeError):
 
    """
 
    Raised if message is missing vital headers, such
 
    as recipients or sender address.
 
    """
 

	
 

	
 
class BadHeaders(RuntimeError):
 
    """
 
    Raised if message contains newlines in headers.
 
    """
rhodecode/lib/rcmail/message.py
Show inline comments
 
from rhodecode.lib.rcmail.response import MailResponse
 

	
 
from rhodecode.lib.rcmail.exceptions import BadHeaders
 
from rhodecode.lib.rcmail.exceptions import InvalidMessage
 

	
 
class Attachment(object):
 
    """
 
    Encapsulates file attachment information.
 

	
 
    :param filename: filename of attachment
 
    :param content_type: file mimetype
 
    :param data: the raw file data, either as string or file obj
 
    :param disposition: content-disposition (if any)
 
    """
 

	
 
    def __init__(self,
 
                 filename=None,
 
                 content_type=None,
 
                 data=None,
 
                 disposition=None):
 

	
 
        self.filename = filename
 
        self.content_type = content_type
 
        self.disposition = disposition or 'attachment'
 
        self._data = data
 

	
 
    @property
 
    def data(self):
 
        if isinstance(self._data, basestring):
 
            return self._data
 
        self._data = self._data.read()
 
        return self._data
 

	
 

	
 
class Message(object):
 
    """
 
    Encapsulates an email message.
 

	
 
    :param subject: email subject header
 
    :param recipients: list of email addresses
 
    :param body: plain text message
 
    :param html: HTML message
 
    :param sender: email sender address
 
    :param cc: CC list
 
    :param bcc: BCC list
 
    :param extra_headers: dict of extra email headers
 
    :param attachments: list of Attachment instances
 
    :param recipients_separator: alternative separator for any of
 
        'From', 'To', 'Delivered-To', 'Cc', 'Bcc' fields
 
    """
 

	
 
    def __init__(self,
 
                 subject=None,
 
                 recipients=None,
 
                 body=None,
 
                 html=None,
 
                 sender=None,
 
                 cc=None,
 
                 bcc=None,
 
                 extra_headers=None,
 
                 attachments=None):
 

	
 
                 attachments=None,
 
                 recipients_separator="; "):
 

	
 
        self.subject = subject or ''
 
        self.sender = sender
 
        self.body = body
 
        self.html = html
 

	
 
        self.recipients = recipients or []
 
        self.attachments = attachments or []
 
        self.cc = cc or []
 
        self.bcc = bcc or []
 
        self.extra_headers = extra_headers or {}
 

	
 
        self.recipients_separator = recipients_separator
 

	
 
    @property
 
    def send_to(self):
 
        return set(self.recipients) | set(self.bcc or ()) | set(self.cc or ())
 

	
 
    def to_message(self):
 
        """
 
        Returns raw email.Message instance.Validates message first.
 
        """
 

	
 
        self.validate()
 

	
 
        return self.get_response().to_message()
 

	
 
    def get_response(self):
 
        """
 
        Creates a Lamson MailResponse instance
 
        """
 

	
 
        response = MailResponse(Subject=self.subject,
 
                                To=self.recipients,
 
                                From=self.sender,
 
                                Body=self.body,
 
                                Html=self.html)
 
                                Html=self.html,
 
                                separator=self.recipients_separator)
 

	
 
        if self.bcc:
 
            response.base['Bcc'] = self.bcc
 

	
 
        if self.cc:
 
            response.base['Cc'] = self.cc
 

	
 
        for attachment in self.attachments:
 

	
 
            response.attach(attachment.filename,
 
                            attachment.content_type,
 
                            attachment.data,
 
                            attachment.disposition)
 

	
 
        response.update(self.extra_headers)
 

	
 
        return response
 

	
 
    def is_bad_headers(self):
 
        """
 
        Checks for bad headers i.e. newlines in subject, sender or recipients.
 
        """
 

	
 
        headers = [self.subject, self.sender]
 
        headers += list(self.send_to)
 
        headers += self.extra_headers.values()
 

	
 
        for val in headers:
 
            for c in '\r\n':
 
                if c in val:
 
                    return True
 
        return False
 

	
 
    def validate(self):
 
        """
 
        Checks if message is valid and raises appropriate exception.
 
        """
 

	
 
        if not self.recipients:
 
            raise InvalidMessage, "No recipients have been added"
 

	
 
        if not self.body and not self.html:
 
            raise InvalidMessage, "No body has been set"
 

	
 
        if not self.sender:
 
            raise InvalidMessage, "No sender address has been set"
 

	
 
        if self.is_bad_headers():
 
            raise BadHeaders
 

	
 
    def add_recipient(self, recipient):
 
        """
 
        Adds another recipient to the message.
 

	
 
        :param recipient: email address of recipient.
 
        """
 

	
 
        self.recipients.append(recipient)
 

	
 
    def add_cc(self, recipient):
 
        """
 
        Adds an email address to the CC list.
 

	
 
        :param recipient: email address of recipient.
 
        """
 

	
 
        self.cc.append(recipient)
 

	
 
    def add_bcc(self, recipient):
 
        """
 
        Adds an email address to the BCC list.
 

	
 
        :param recipient: email address of recipient.
 
        """
 

	
 
        self.bcc.append(recipient)
 

	
 
    def attach(self, attachment):
 
        """
 
        Adds an attachment to the message.
 

	
 
        :param attachment: an **Attachment** instance.
 
        """
 

	
 
        self.attachments.append(attachment)
rhodecode/lib/rcmail/response.py
Show inline comments
 
# The code in this module is entirely lifted from the Lamson project
 
# (http://lamsonproject.org/).  Its copyright is:
 

	
 
# Copyright (c) 2008, Zed A. Shaw
 
# All rights reserved.
 

	
 
# It is provided under this license:
 

	
 
# Redistribution and use in source and binary forms, with or without
 
# modification, are permitted provided that the following conditions are met:
 

	
 
# * Redistributions of source code must retain the above copyright notice, this
 
#   list of conditions and the following disclaimer.
 

	
 
# * Redistributions in binary form must reproduce the above copyright notice,
 
#   this list of conditions and the following disclaimer in the documentation
 
#   and/or other materials provided with the distribution.
 

	
 
# * Neither the name of the Zed A. Shaw nor the names of its contributors may
 
#   be used to endorse or promote products derived from this software without
 
#   specific prior written permission.
 

	
 
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 
# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
 
# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 
# POSSIBILITY OF SUCH DAMAGE.
 

	
 
import os
 
import mimetypes
 
import string
 
from email import encoders
 
from email.charset import Charset
 
from email.utils import parseaddr
 
from email.mime.base import MIMEBase
 

	
 
ADDRESS_HEADERS_WHITELIST = ['From', 'To', 'Delivered-To', 'Cc', 'Bcc']
 
DEFAULT_ENCODING = "utf-8"
 
VALUE_IS_EMAIL_ADDRESS = lambda v: '@' in v
 

	
 
def normalize_header(header):
 
    return string.capwords(header.lower(), '-')
 

	
 
class EncodingError(Exception):
 
    """Thrown when there is an encoding error."""
 
    pass
 

	
 
class MailBase(object):
 
    """MailBase is used as the basis of lamson.mail and contains the basics of
 
    encoding an email.  You actually can do all your email processing with this
 
    class, but it's more raw.
 
    """
 
    def __init__(self, items=()):
 
        self.headers = dict(items)
 
        self.parts = []
 
        self.body = None
 
        self.content_encoding = {'Content-Type': (None, {}),
 
                                 'Content-Disposition': (None, {}),
 
                                 'Content-Transfer-Encoding': (None, {})}
 

	
 
    def __getitem__(self, key):
 
        return self.headers.get(normalize_header(key), None)
 

	
 
    def __len__(self):
 
        return len(self.headers)
 

	
 
    def __iter__(self):
 
        return iter(self.headers)
 

	
 
    def __contains__(self, key):
 
        return normalize_header(key) in self.headers
 

	
 
    def __setitem__(self, key, value):
 
        self.headers[normalize_header(key)] = value
 

	
 
    def __delitem__(self, key):
 
        del self.headers[normalize_header(key)]
 

	
 
    def __nonzero__(self):
 
        return self.body != None or len(self.headers) > 0 or len(self.parts) > 0
 

	
 
    def keys(self):
 
        """Returns the sorted keys."""
 
        return sorted(self.headers.keys())
 

	
 
    def attach_file(self, filename, data, ctype, disposition):
 
        """
 
        A file attachment is a raw attachment with a disposition that
 
        indicates the file name.
 
        """
 
        assert filename, "You can't attach a file without a filename."
 
        ctype = ctype.lower()
 

	
 
        part = MailBase()
 
        part.body = data
 
        part.content_encoding['Content-Type'] = (ctype, {'name': filename})
 
        part.content_encoding['Content-Disposition'] = (disposition,
 
                                                        {'filename': filename})
 
        self.parts.append(part)
 

	
 

	
 
    def attach_text(self, data, ctype):
 
        """
 
        This attaches a simpler text encoded part, which doesn't have a
 
        filename.
 
        """
 
        ctype = ctype.lower()
 

	
 
        part = MailBase()
 
        part.body = data
 
        part.content_encoding['Content-Type'] = (ctype, {})
 
        self.parts.append(part)
 

	
 
    def walk(self):
 
        for p in self.parts:
 
            yield p
 
            for x in p.walk():
 
                yield x
 

	
 
class MailResponse(object):
 
    """
 
    You are given MailResponse objects from the lamson.view methods, and
 
    whenever you want to generate an email to send to someone.  It has the
 
    same basic functionality as MailRequest, but it is designed to be written
 
    to, rather than read from (although you can do both).
 

	
 
    You can easily set a Body or Html during creation or after by passing it
 
    as __init__ parameters, or by setting those attributes.
 

	
 
    You can initially set the From, To, and Subject, but they are headers so
 
    use the dict notation to change them: msg['From'] = 'joe@test.com'.
 

	
 
    The message is not fully crafted until right when you convert it with
 
    MailResponse.to_message.  This lets you change it and work with it, then
 
    send it out when it's ready.
 
    """
 
    def __init__(self, To=None, From=None, Subject=None, Body=None, Html=None):
 
    def __init__(self, To=None, From=None, Subject=None, Body=None, Html=None, 
 
                 separator="; "):
 
        self.Body = Body
 
        self.Html = Html
 
        self.base = MailBase([('To', To), ('From', From), ('Subject', Subject)])
 
        self.multipart = self.Body and self.Html
 
        self.attachments = []
 
        self.separator = separator
 

	
 
    def __contains__(self, key):
 
        return self.base.__contains__(key)
 

	
 
    def __getitem__(self, key):
 
        return self.base.__getitem__(key)
 

	
 
    def __setitem__(self, key, val):
 
        return self.base.__setitem__(key, val)
 

	
 
    def __delitem__(self, name):
 
        del self.base[name]
 

	
 
    def attach(self, filename=None, content_type=None, data=None,
 
               disposition=None):
 
        """
 

	
 
        Simplifies attaching files from disk or data as files.  To attach
 
        simple text simple give data and a content_type.  To attach a file,
 
        give the data/content_type/filename/disposition combination.
 

	
 
        For convenience, if you don't give data and only a filename, then it
 
        will read that file's contents when you call to_message() later.  If
 
        you give data and filename then it will assume you've filled data
 
        with what the file's contents are and filename is just the name to
 
        use.
 
        """
 

	
 
        assert filename or data, ("You must give a filename or some data to "
 
                                  "attach.")
 
        assert data or os.path.exists(filename), ("File doesn't exist, and no "
 
                                                  "data given.")
 

	
 
        self.multipart = True
 

	
 
        if filename and not content_type:
 
            content_type, encoding = mimetypes.guess_type(filename)
 

	
 
        assert content_type, ("No content type given, and couldn't guess "
 
                              "from the filename: %r" % filename)
 

	
 
        self.attachments.append({'filename': filename,
 
                                 'content_type': content_type,
 
                                 'data': data,
 
                                 'disposition': disposition,})
 
    def attach_part(self, part):
 
        """
 
        Attaches a raw MailBase part from a MailRequest (or anywhere)
 
        so that you can copy it over.
 
        """
 
        self.multipart = True
 

	
 
        self.attachments.append({'filename': None,
 
                                 'content_type': None,
 
                                 'data': None,
 
                                 'disposition': None,
 
                                 'part': part,
 
                                 })
 

	
 
    def attach_all_parts(self, mail_request):
 
        """
 
        Used for copying the attachment parts of a mail.MailRequest
 
        object for mailing lists that need to maintain attachments.
 
        """
 
        for part in mail_request.all_parts():
 
            self.attach_part(part)
 

	
 
        self.base.content_encoding = mail_request.base.content_encoding.copy()
 

	
 
    def clear(self):
 
        """
 
        Clears out the attachments so you can redo them.  Use this to keep the
 
        headers for a series of different messages with different attachments.
 
        """
 
        del self.attachments[:]
 
        del self.base.parts[:]
 
        self.multipart = False
 

	
 

	
 
    def update(self, message):
 
        """
 
        Used to easily set a bunch of heading from another dict
 
        like object.
 
        """
 
        for k in message.keys():
 
            self.base[k] = message[k]
 

	
 
    def __str__(self):
 
        """
 
        Converts to a string.
 
        """
 
        return self.to_message().as_string()
 

	
 
    def _encode_attachment(self, filename=None, content_type=None, data=None,
 
                           disposition=None, part=None):
 
        """
 
        Used internally to take the attachments mentioned in self.attachments
 
        and do the actual encoding in a lazy way when you call to_message.
 
        """
 
        if part:
 
            self.base.parts.append(part)
 
        elif filename:
 
            if not data:
 
                data = open(filename).read()
 

	
 
            self.base.attach_file(filename, data, content_type,
 
                                  disposition or 'attachment')
 
        else:
 
            self.base.attach_text(data, content_type)
 

	
 
        ctype = self.base.content_encoding['Content-Type'][0]
 

	
 
        if ctype and not ctype.startswith('multipart'):
 
            self.base.content_encoding['Content-Type'] = ('multipart/mixed', {})
 

	
 
    def to_message(self):
 
        """
 
        Figures out all the required steps to finally craft the
 
        message you need and return it.  The resulting message
 
        is also available as a self.base attribute.
 

	
 
        What is returned is a Python email API message you can
 
        use with those APIs.  The self.base attribute is the raw
 
        lamson.encoding.MailBase.
 
        """
 
        del self.base.parts[:]
 

	
 
        if self.Body and self.Html:
 
            self.multipart = True
 
            self.base.content_encoding['Content-Type'] = (
 
                'multipart/alternative', {})
 

	
 
        if self.multipart:
 
            self.base.body = None
 
            if self.Body:
 
                self.base.attach_text(self.Body, 'text/plain')
 

	
 
            if self.Html:
 
                self.base.attach_text(self.Html, 'text/html')
 

	
 
            for args in self.attachments:
 
                self._encode_attachment(**args)
 

	
 
        elif self.Body:
 
            self.base.body = self.Body
 
            self.base.content_encoding['Content-Type'] = ('text/plain', {})
 

	
 
        elif self.Html:
 
            self.base.body = self.Html
 
            self.base.content_encoding['Content-Type'] = ('text/html', {})
 

	
 
        return to_message(self.base)
 
        return to_message(self.base, separator=self.separator)
 

	
 
    def all_parts(self):
 
        """
 
        Returns all the encoded parts.  Only useful for debugging
 
        or inspecting after calling to_message().
 
        """
 
        return self.base.parts
 

	
 
    def keys(self):
 
        return self.base.keys()
 

	
 
def to_message(mail):
 
def to_message(mail, separator="; "):
 
    """
 
    Given a MailBase message, this will construct a MIMEPart
 
    that is canonicalized for use with the Python email API.
 
    """
 
    ctype, params = mail.content_encoding['Content-Type']
 

	
 
    if not ctype:
 
        if mail.parts:
 
            ctype = 'multipart/mixed'
 
        else:
 
            ctype = 'text/plain'
 
    else:
 
        if mail.parts:
 
            assert ctype.startswith(("multipart", "message")), \
 
                   "Content type should be multipart or message, not %r" % ctype
 

	
 
    # adjust the content type according to what it should be now
 
    mail.content_encoding['Content-Type'] = (ctype, params)
 

	
 
    try:
 
        out = MIMEPart(ctype, **params)
 
    except TypeError, exc: # pragma: no cover
 
        raise EncodingError("Content-Type malformed, not allowed: %r; "
 
                            "%r (Python ERROR: %s" %
 
                            (ctype, params, exc.message))
 

	
 
    for k in mail.keys():
 
        if k in ADDRESS_HEADERS_WHITELIST:
 
            out[k.encode('ascii')] = header_to_mime_encoding(mail[k])
 
            out[k.encode('ascii')] = header_to_mime_encoding(
 
                                         mail[k],
 
                                         not_email=False,
 
                                         separator=separator
 
                                     )
 
        else:
 
            out[k.encode('ascii')] = header_to_mime_encoding(mail[k],
 
                                                             not_email=True)
 
            out[k.encode('ascii')] = header_to_mime_encoding(
 
                                         mail[k],
 
                                         not_email=True
 
                                    )
 

	
 
    out.extract_payload(mail)
 

	
 
    # go through the children
 
    for part in mail.parts:
 
        out.attach(to_message(part))
 

	
 
    return out
 

	
 
class MIMEPart(MIMEBase):
 
    """
 
    A reimplementation of nearly everything in email.mime to be more useful
 
    for actually attaching things.  Rather than one class for every type of
 
    thing you'd encode, there's just this one, and it figures out how to
 
    encode what you ask it.
 
    """
 
    def __init__(self, type, **params):
 
        self.maintype, self.subtype = type.split('/')
 
        MIMEBase.__init__(self, self.maintype, self.subtype, **params)
 

	
 
    def add_text(self, content):
 
        # this is text, so encode it in canonical form
 
        try:
 
            encoded = content.encode('ascii')
 
            charset = 'ascii'
 
        except UnicodeError:
 
            encoded = content.encode('utf-8')
 
            charset = 'utf-8'
 

	
 
        self.set_payload(encoded, charset=charset)
 

	
 

	
 
    def extract_payload(self, mail):
 
        if mail.body == None: return  # only None, '' is still ok
 

	
 
        ctype, ctype_params = mail.content_encoding['Content-Type']
 
        cdisp, cdisp_params = mail.content_encoding['Content-Disposition']
 

	
 
        assert ctype, ("Extract payload requires that mail.content_encoding "
 
                       "have a valid Content-Type.")
 

	
 
        if ctype.startswith("text/"):
 
            self.add_text(mail.body)
 
        else:
 
            if cdisp:
 
                # replicate the content-disposition settings
 
                self.add_header('Content-Disposition', cdisp, **cdisp_params)
 

	
 
            self.set_payload(mail.body)
 
            encoders.encode_base64(self)
 

	
 
    def __repr__(self):
 
        return "<MIMEPart '%s/%s': %r, %r, multipart=%r>" % (
 
            self.subtype,
 
            self.maintype,
 
            self['Content-Type'],
 
            self['Content-Disposition'],
 
            self.is_multipart())
 

	
 

	
 
def header_to_mime_encoding(value, not_email=False):
 
def header_to_mime_encoding(value, not_email=False, separator=", "):
 
    if not value: return ""
 

	
 
    encoder = Charset(DEFAULT_ENCODING)
 
    if type(value) == list:
 
        return "; ".join(properly_encode_header(
 
        return separator.join(properly_encode_header(
 
            v, encoder, not_email) for v in value)
 
    else:
 
        return properly_encode_header(value, encoder, not_email)
 

	
 
def properly_encode_header(value, encoder, not_email):
 
    """
 
    The only thing special (weird) about this function is that it tries
 
    to do a fast check to see if the header value has an email address in
 
    it.  Since random headers could have an email address, and email addresses
 
    have weird special formatting rules, we have to check for it.
 

	
 
    Normally this works fine, but in Librelist, we need to "obfuscate" email
 
    addresses by changing the '@' to '-AT-'.  This is where
 
    VALUE_IS_EMAIL_ADDRESS exists.  It's a simple lambda returning True/False
 
    to check if a header value has an email address.  If you need to make this
 
    check different, then change this.
 
    """
 
    try:
 
        return value.encode("ascii")
 
    except UnicodeEncodeError:
 
        if not_email is False and VALUE_IS_EMAIL_ADDRESS(value):
 
            # this could have an email address, make sure we don't screw it up
 
            name, address = parseaddr(value)
 
            return '"%s" <%s>' % (
 
                encoder.header_encode(name.encode("utf-8")), address)
 

	
 
        return encoder.header_encode(value.encode("utf-8"))
rhodecode/lib/rcmail/smtp_mailer.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
"""
 
    rhodecode.lib.rcmail.smtp_mailer
 
    ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 

	
 
    Simple smtp mailer used in RhodeCode
 

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

	
 
import logging
 
import smtplib
 
from socket import sslerror
 
from rhodecode.lib.rcmail.message import Message
 

	
 

	
 
class SmtpMailer(object):
 
    """SMTP mailer class
 

	
 
    mailer = SmtpMailer(mail_from, user, passwd, mail_server, smtp_auth
 
                        mail_port, ssl, tls)
 
    mailer.send(recipients, subject, body, attachment_files)
 

	
 
    :param recipients might be a list of string or single string
 
    :param attachment_files is a dict of {filename:location}
 
        it tries to guess the mimetype and attach the file
 

	
 
    """
 

	
 
    def __init__(self, mail_from, user, passwd, mail_server, smtp_auth=None,
 
                 mail_port=None, ssl=False, tls=False, debug=False):
 

	
 
        self.mail_from = mail_from
 
        self.mail_server = mail_server
 
        self.mail_port = mail_port
 
        self.user = user
 
        self.passwd = passwd
 
        self.ssl = ssl
 
        self.tls = tls
 
        self.debug = debug
 
        self.auth = smtp_auth
 

	
 
    def send(self, recipients=[], subject='', body='', html='',
 
             attachment_files=None):
 

	
 
        if isinstance(recipients, basestring):
 
            recipients = [recipients]
 
        msg = Message(subject, recipients, body, html, self.mail_from)
 
        msg = Message(subject, recipients, body, html, self.mail_from,
 
                      recipients_separator=", ")
 
        raw_msg = msg.to_message()
 

	
 
        if self.ssl:
 
            smtp_serv = smtplib.SMTP_SSL(self.mail_server, self.mail_port)
 
        else:
 
            smtp_serv = smtplib.SMTP(self.mail_server, self.mail_port)
 

	
 
        if self.tls:
 
            smtp_serv.ehlo()
 
            smtp_serv.starttls()
 

	
 
        if self.debug:
 
            smtp_serv.set_debuglevel(1)
 

	
 
        smtp_serv.ehlo()
 
        if self.auth:
 
            smtp_serv.esmtp_features["auth"] = self.auth
 

	
 
        # if server requires authorization you must provide login and password
 
        # but only if we have them
 
        if self.user and self.passwd:
 
            smtp_serv.login(self.user, self.passwd)
 

	
 
        smtp_serv.sendmail(msg.sender, msg.send_to, raw_msg.as_string())
 
        logging.info('MAIL SEND TO: %s' % recipients)
 

	
 
        try:
 
            smtp_serv.quit()
 
        except sslerror:
 
            # sslerror is raised in tls connections on closing sometimes
 
            pass
rhodecode/lib/utils.py
Show inline comments
 
# -*- coding: utf-8 -*-
 
"""
 
    rhodecode.lib.utils
 
    ~~~~~~~~~~~~~~~~~~~
 

	
 
    Utilities library for RhodeCode
 

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

	
 
import os
 
import logging
 
import datetime
 
import traceback
 
import paste
 
import beaker
 
import tarfile
 
import shutil
 
from os.path import abspath
 
from os.path import dirname as dn, join as jn
 

	
 
from paste.script.command import Command, BadCommand
 

	
 
from mercurial import ui, config
 

	
 
from webhelpers.text import collapse, remove_formatting, strip_tags
 

	
 
from vcs import get_backend
 
from vcs.backends.base import BaseChangeset
 
from vcs.utils.lazy import LazyProperty
 
from vcs.utils.helpers import get_scm
 
from vcs.exceptions import VCSError
 

	
 
from rhodecode.lib.caching_query import FromCache
 

	
 
from rhodecode.model import meta
 
from rhodecode.model.db import Repository, User, RhodeCodeUi, \
 
    UserLog, RepoGroup, RhodeCodeSetting
 
from rhodecode.model.meta import Session
 

	
 
log = logging.getLogger(__name__)
 

	
 

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

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

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

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

	
 

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

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

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

	
 

	
 
def get_repo_slug(request):
 
    return request.environ['pylons.routes_dict'].get('repo_name')
 

	
 

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

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

	
 
    """
 

	
 
    if not sa:
 
        sa = meta.Session
 

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

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

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

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

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

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

	
 

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

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

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

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

	
 
    return _get_repos(path)
 

	
 

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

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

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

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

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

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

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

	
 
    return False
 

	
 
def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
 
    while True:
 
        ok = raw_input(prompt)
 
        if ok in ('y', 'ye', 'yes'):
 
            return True
 
        if ok in ('n', 'no', 'nop', 'nope'):
 
            return False
 
        retries = retries - 1
 
        if retries < 0:
 
            raise IOError
 
        print complaint
 

	
 
#propagated from mercurial documentation
 
ui_sections = ['alias', 'auth',
 
                'decode/encode', 'defaults',
 
                'diff', 'email',
 
                'extensions', 'format',
 
                'merge-patterns', 'merge-tools',
 
                'hooks', 'http_proxy',
 
                'smtp', 'patch',
 
                'paths', 'profiling',
 
                'server', 'trusted',
 
                'ui', 'web', ]
 

	
 

	
 
def make_ui(read_from='file', path=None, checkpaths=True):
 
    """A function that will read python rc files or database
 
    and make an mercurial ui object from read options
 

	
 
    :param path: path to mercurial config file
 
    :param checkpaths: check the path
 
    :param read_from: read from 'file' or 'db'
 
    """
 

	
 
    baseui = ui.ui()
 

	
 
    #clean the baseui object
 
    baseui._ocfg = config.config()
 
    baseui._ucfg = config.config()
 
    baseui._tcfg = config.config()
 

	
 
    if read_from == 'file':
 
        if not os.path.isfile(path):
 
            log.warning('Unable to read config file %s' % path)
 
            return False
 
        log.debug('reading hgrc from %s', path)
 
        cfg = config.config()
 
        cfg.read(path)
 
        for section in ui_sections:
 
            for k, v in cfg.items(section):
 
                log.debug('settings ui from file[%s]%s:%s', section, k, v)
 
                baseui.setconfig(section, k, v)
 

	
 
    elif read_from == 'db':
 
        sa = meta.Session
 
        ret = sa.query(RhodeCodeUi)\
 
            .options(FromCache("sql_cache_short",
 
                               "get_hg_ui_settings")).all()
 

	
 
        hg_ui = ret
 
        for ui_ in hg_ui:
 
            if ui_.ui_active:
 
                log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
 
                          ui_.ui_key, ui_.ui_value)
 
                baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
 

	
 
        meta.Session.remove()
 
    return baseui
 

	
 

	
 
def set_rhodecode_config(config):
 
    """
 
    Updates pylons config with new settings from database
 

	
 
    :param config:
 
    """
 
    hgsettings = RhodeCodeSetting.get_app_settings()
 

	
 
    for k, v in hgsettings.items():
 
        config[k] = v
 

	
 

	
 
def invalidate_cache(cache_key, *args):
 
    """
 
    Puts cache invalidation task into db for
 
    further global cache invalidation
 
    """
 

	
 
    from rhodecode.model.scm import ScmModel
 

	
 
    if cache_key.startswith('get_repo_cached_'):
 
        name = cache_key.split('get_repo_cached_')[-1]
 
        ScmModel().mark_for_invalidation(name)
 

	
 

	
 
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):
 
        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''
 

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

	
 
    Model for notifications
 

	
 

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

	
 
import os
 
import logging
 
import traceback
 
import datetime
 

	
 
from pylons.i18n.translation import _
 

	
 
import rhodecode
 
from rhodecode.lib import helpers as h
 
from rhodecode.model import BaseModel
 
from rhodecode.model.db import Notification, User, UserNotification
 

	
 
log = logging.getLogger(__name__)
 

	
 

	
 
class NotificationModel(BaseModel):
 

	
 
    def __get_user(self, user):
 
        if isinstance(user, basestring):
 
            return User.get_by_username(username=user)
 
        else:
 
            return self._get_instance(User, user)
 

	
 
    def __get_notification(self, notification):
 
        if isinstance(notification, Notification):
 
            return notification
 
        elif isinstance(notification, int):
 
            return Notification.get(notification)
 
        else:
 
            if notification:
 
                raise Exception('notification must be int or Instance'
 
                                ' of Notification got %s' % type(notification))
 

	
 
    def create(self, created_by, subject, body, recipients=None,
 
               type_=Notification.TYPE_MESSAGE, with_email=True,
 
               email_kwargs={}):
 
        """
 

	
 
        Creates notification of given type
 

	
 
        :param created_by: int, str or User instance. User who created this
 
            notification
 
        :param subject:
 
        :param body:
 
        :param recipients: list of int, str or User objects, when None
 
            is given send to all admins
 
        :param type_: type of notification
 
        :param with_email: send email with this notification
 
        :param email_kwargs: additional dict to pass as args to email template
 
        """
 
        from rhodecode.lib.celerylib import tasks, run_task
 

	
 
        if recipients and not getattr(recipients, '__iter__', False):
 
            raise Exception('recipients must be a list of iterable')
 

	
 
        created_by_obj = self.__get_user(created_by)
 

	
 
        if recipients:
 
            recipients_objs = []
 
            for u in recipients:
 
                obj = self.__get_user(u)
 
                if obj:
 
                    recipients_objs.append(obj)
 
            recipients_objs = set(recipients_objs)
 
        else:
 
            # empty recipients means to all admins
 
            recipients_objs = User.query().filter(User.admin == True).all()
 

	
 
        notif = Notification.create(created_by=created_by_obj, subject=subject,
 
                                    body=body, recipients=recipients_objs,
 
                                    type_=type_)
 

	
 
        if with_email is False:
 
            return notif
 

	
 
        # send email with notification
 
        for rec in recipients_objs:
 
            email_subject = NotificationModel().make_description(notif, False)
 
            type_ = type_
 
            email_body = body
 
            kwargs = {'subject': subject, 'body': h.rst_w_mentions(body)}
 
            kwargs.update(email_kwargs)
 
            email_body_html = EmailNotificationModel()\
 
                                .get_email_tmpl(type_, **kwargs)
 
            run_task(tasks.send_email, rec.email, email_subject, email_body,
 
                     email_body_html)
 

	
 
        return notif
 

	
 
    def delete(self, user, notification):
 
        # we don't want to remove actual notification just the assignment
 
        try:
 
            notification = self.__get_notification(notification)
 
            user = self.__get_user(user)
 
            if notification and user:
 
                obj = UserNotification.query()\
 
                        .filter(UserNotification.user == user)\
 
                        .filter(UserNotification.notification
 
                                == notification)\
 
                        .one()
 
                self.sa.delete(obj)
 
                return True
 
        except Exception:
 
            log.error(traceback.format_exc())
 
            raise
 

	
 
    def get_for_user(self, user):
 
        user = self.__get_user(user)
 
        return user.notifications
 

	
 
    def mark_all_read_for_user(self, user):
 
        user = self.__get_user(user)
 
        UserNotification.query()\
 
            .filter(UserNotification.read==False)\
 
            .update({'read': True})
 

	
 
    def get_unread_cnt_for_user(self, user):
 
        user = self.__get_user(user)
 
        return UserNotification.query()\
 
                .filter(UserNotification.read == False)\
 
                .filter(UserNotification.user == user).count()
 

	
 
    def get_unread_for_user(self, user):
 
        user = self.__get_user(user)
 
        return [x.notification for x in UserNotification.query()\
 
                .filter(UserNotification.read == False)\
 
                .filter(UserNotification.user == user).all()]
 

	
 
    def get_user_notification(self, user, notification):
 
        user = self.__get_user(user)
 
        notification = self.__get_notification(notification)
 

	
 
        return UserNotification.query()\
 
            .filter(UserNotification.notification == notification)\
 
            .filter(UserNotification.user == user).scalar()
 

	
 
    def make_description(self, notification, show_age=True):
 
        """
 
        Creates a human readable description based on properties
 
        of notification object
 
        """
 

	
 
        _map = {notification.TYPE_CHANGESET_COMMENT:_('commented on commit'),
 
                notification.TYPE_MESSAGE:_('sent message'),
 
                notification.TYPE_MENTION:_('mentioned you'),
 
                notification.TYPE_REGISTRATION:_('registered in RhodeCode')}
 

	
 
        DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S"
 

	
 
        tmpl = "%(user)s %(action)s %(when)s"
 
        if show_age:
 
            when = h.age(notification.created_on)
 
        else:
 
            DTF = lambda d: datetime.datetime.strftime(d, DATETIME_FORMAT)
 
            when = DTF(notification.created_on)
 
        data = dict(user=notification.created_by_user.username,
 
                    action=_map[notification.type_],
 
                    when=when)
 
        return tmpl % data
 

	
 

	
 
class EmailNotificationModel(BaseModel):
 

	
 
    TYPE_CHANGESET_COMMENT = Notification.TYPE_CHANGESET_COMMENT
 
    TYPE_PASSWORD_RESET = 'passoword_link'
 
    TYPE_REGISTRATION = Notification.TYPE_REGISTRATION
 
    TYPE_DEFAULT = 'default'
 

	
 
    def __init__(self):
 
        self._template_root = rhodecode.CONFIG['pylons.paths']['templates'][0]
rhodecode/templates/changelog/changelog.html
Show inline comments
 
## -*- coding: utf-8 -*-
 

	
 
<%inherit file="/base/base.html"/>
 

	
 
<%def name="title()">
 
${c.repo_name} ${_('Changelog')} - ${c.rhodecode_name}
 
</%def>
 

	
 
<%def name="breadcrumbs_links()">
 
    ${h.link_to(u'Home',h.url('/'))}
 
    &raquo;
 
    ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
 
    &raquo;
 
    ${_('Changelog')} - ${_('showing ')} ${c.size if c.size <= c.total_cs else c.total_cs} ${_('out of')} ${c.total_cs} ${_('revisions')}  
 
</%def>
 

	
 
<%def name="page_nav()">
 
	${self.menu('changelog')}     
 
</%def>
 

	
 
<%def name="main()">
 
<div class="box">
 
    <!-- box / title -->
 
    <div class="title">
 
        ${self.breadcrumbs()}
 
    </div>
 
    <div class="table">
 
		% if c.pagination:
 
			<div id="graph">
 
				<div id="graph_nodes">
 
					<canvas id="graph_canvas"></canvas>
 
				</div>
 
				<div id="graph_content">
 
					<div class="container_header">
 
				        ${h.form(h.url.current(),method='get')}
 
				        <div class="info_box" style="float:left">
 
				          ${h.submit('set',_('Show'),class_="ui-btn")}
 
				          ${h.text('size',size=1,value=c.size)}
 
				          ${_('revisions')}
 
				        </div>
 
				        ${h.end_form()}
 
					<div id="rev_range_container" style="display:none"></div>
 
                    <div style="float:right">${h.select('branch_filter',c.branch_name,c.branch_filters)}</div>
 
					</div>
 
					
 
				%for cnt,cs in enumerate(c.pagination):
 
					<div id="chg_${cnt+1}" class="container ${'tablerow1' if cnt%2==0 else 'tablerow2'}">
 
						<div class="left">
 
							<div>
 
							${h.checkbox(cs.short_id,class_="changeset_range")}
 
							<span class="changeset_id">${cs.revision}:<span class="changeset_hash">${h.short_id(cs.raw_id)}</span></span>
 
							<span class="tooltip" title="${cs.date}"><a href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id)}"><span class="changeset_id">${cs.revision}:<span class="changeset_hash">${h.short_id(cs.raw_id)}</span></span></a></span>
 
							</div>
 
							<div class="author">
 
								<div class="gravatar">
 
									<img alt="gravatar" src="${h.gravatar_url(h.email(cs.author),16)}"/>
 
								</div>
 
								<div title="${cs.author}" class="user">${h.person(cs.author)}</div>
 
							</div>
 
							<div class="date">${cs.date}</div>
 
						</div>
 
						<div class="mid">
 
							<div class="message">${h.link_to(h.wrap_paragraphs(cs.message),h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}</div>
 
						</div>	
 
						<div class="right">
 
									<div id="${cs.raw_id}_changes_info" class="changes">
 
                                        <span id="${cs.raw_id}" class="changed_total tooltip" title="${_('Affected number of files, click to show more details')}">${len(cs.affected_files)}</span>
 
									</div>					
 
								   %if cs.parents:
 
									%for p_cs in reversed(cs.parents):
 
										<div class="parent">${_('Parent')}
 
											<span class="changeset_id">${p_cs.revision}:<span class="changeset_hash">${h.link_to(h.short_id(p_cs.raw_id),
 
											h.url('changeset_home',repo_name=c.repo_name,revision=p_cs.raw_id),title=p_cs.message)}</span></span>
 
										</div>
 
									%endfor
 
								   %else:	
 
                                        <div class="parent">${_('No parents')}</div>   
 
                                   %endif  
 
                    									
 
								<span class="logtags">
 
									%if len(cs.parents)>1:
 
									<span class="merge">${_('merge')}</span>
 
									%endif
 
									%if cs.branch:
 
									<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.raw_id))}</span>
 
									%endif
 
									%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.raw_id))}</span>
 
									%endfor
 
								</span>																	
 
						</div>				
 
					</div>
 
					
 
				%endfor
 
				<div class="pagination-wh pagination-left">
 
					${c.pagination.pager('$link_previous ~2~ $link_next')}
 
				</div>			
 
				</div>
 
			</div>
 
			
 
			<script type="text/javascript" src="${h.url('/js/graph.js')}"></script>
 
			<script type="text/javascript">
 
				YAHOO.util.Event.onDOMReady(function(){
 
					
 
                    //Monitor range checkboxes and build a link to changesets
 
                    //ranges 
 
                    var checkboxes = YUD.getElementsByClassName('changeset_range');
 
                    var url_tmpl = "${h.url('changeset_home',repo_name=c.repo_name,revision='__REVRANGE__')}";
 
                    YUE.on(checkboxes,'click',function(e){      
 
                        var checked_checkboxes = [];
 
                        for (pos in checkboxes){
 
                            if(checkboxes[pos].checked){
 
                                checked_checkboxes.push(checkboxes[pos]);
 
                            }
 
                        }
 
                        if(checked_checkboxes.length>1){
 
                        	var rev_end = checked_checkboxes[0].name;
 
                        	var rev_start = checked_checkboxes[checked_checkboxes.length-1].name;
 
                        	
 
                            var url = url_tmpl.replace('__REVRANGE__',
 
                            		rev_start+'...'+rev_end);
 
                            
 
                        var link = "<a href="+url+">${_('Show selected changes __S -> __E')}</a>"
 
                        link = link.replace('__S',rev_start);
 
                        link = link.replace('__E',rev_end);
 
                        YUD.get('rev_range_container').innerHTML = link;
 
                        YUD.setStyle('rev_range_container','display','');
 
                        }
 
                        else{
 
                        	YUD.setStyle('rev_range_container','display','none');
 
                        	
 
                        }
 
                    });					
 
					
 
                    // Fetch changeset details 
 
                    YUE.on(YUD.getElementsByClassName('changed_total'),'click',function(e){
 
                    	var id = e.currentTarget.id
 
                    	var url = "${h.url('changelog_details',repo_name=c.repo_name,cs='__CS__')}"
 
                    	var url = url.replace('__CS__',id);
 
                    	ypjax(url,id+'_changes_info',function(){tooltip_activate()});
 
                    });
 
                    
 
                    // change branch filter
 
                    YUE.on(YUD.get('branch_filter'),'change',function(e){
 
                    	var selected_branch = e.currentTarget.options[e.currentTarget.selectedIndex].value;
 
                    	console.log(selected_branch);
 
                    	var url_main = "${h.url('changelog_home',repo_name=c.repo_name)}";
 
                    	var url = "${h.url('changelog_home',repo_name=c.repo_name,branch='__BRANCH__')}";
 
                    	var url = url.replace('__BRANCH__',selected_branch);
 
                    	if(selected_branch != ''){
 
                    		window.location = url;
 
                    	}else{
 
                    		window.location = url_main;
 
                    	}
 
                        
 
                    });
 
                    
 
					function set_canvas(heads) {
 
						var c = document.getElementById('graph_nodes');
 
						var t = document.getElementById('graph_content');
 
						canvas = document.getElementById('graph_canvas');
 
						var div_h = t.clientHeight;
 
						c.style.height=div_h+'px';
 
						canvas.setAttribute('height',div_h);
 
						c.style.height=max_w+'px';
 
						canvas.setAttribute('width',max_w);
 
					};
 
					var heads = 1;
 
					var max_heads = 0;
 
					var jsdata = ${c.jsdata|n};
 
					
 
					for( var i=0;i<jsdata.length;i++){
 
					    var m = Math.max.apply(Math, jsdata[i][1]);
 
					    if (m>max_heads){
 
					        max_heads = m;
 
					    }
 
					}
 
					var max_w = Math.max(100,max_heads*25);
 
					set_canvas(max_w);
 
					
 
					var r = new BranchRenderer();
 
					r.render(jsdata,max_w);
 
										
 
				});
 
			</script>
 
		%else:
 
			${_('There are no changes yet')}
 
		%endif  
 
    </div>
 
</div>    
 
</%def>
 
\ No newline at end of file
rhodecode/templates/changeset/changeset.html
Show inline comments
 
## -*- coding: utf-8 -*-
 

	
 
<%inherit file="/base/base.html"/>
 

	
 
<%def name="title()">
 
    ${c.repo_name} ${_('Changeset')} - r${c.changeset.revision}:${h.short_id(c.changeset.raw_id)} - ${c.rhodecode_name}
 
</%def>
 

	
 
<%def name="breadcrumbs_links()">
 
    ${h.link_to(u'Home',h.url('/'))}
 
    &raquo;
 
    ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
 
    &raquo;
 
    ${_('Changeset')} - r${c.changeset.revision}:${h.short_id(c.changeset.raw_id)}
 
</%def>
 

	
 
<%def name="page_nav()">
 
    ${self.menu('changelog')}     
 
</%def>
 

	
 
<%def name="main()">
 
<div class="box">
 
    <!-- box / title -->
 
    <div class="title">
 
        ${self.breadcrumbs()}
 
    </div>
 
    <div class="table">
 
		<div class="diffblock">
 
			<div class="code-header">
 
                <div class="date">${c.changeset.revision}:
 
                  ${h.link_to(h.short_id(c.changeset.raw_id),h.url('changeset_home',repo_name=c.repo_name,revision=c.changeset.raw_id))}
 
                  ${c.changeset.date}</div>
 
                <span class="diff-actions">
 
                  <a href="${h.url('raw_changeset_home',repo_name=c.repo_name,revision=c.changeset.raw_id,diff='show')}" title="${_('raw diff')}"><img class="icon" src="${h.url('/images/icons/page_white_text.png')}"/></a>
 
                  <a href="${h.url('raw_changeset_home',repo_name=c.repo_name,revision=c.changeset.raw_id,diff='download')}" title="${_('download diff')}"><img class="icon" src="${h.url('/images/icons/down_16.png')}"/></a>
 
                  ${c.ignorews_url()}
 
                  ${c.context_url()}
 
                </span>
 
                <div class="comments-number" style="float:right;padding-right:5px">${len(c.comments)} comment(s) (${c.inline_cnt} ${_('inline')})</div>
 
			</div>
 
		</div>
 
	    <div id="changeset_content">
 
			<div class="container">
 
	             <div class="left">
 
	                 <div class="author">
 
	                     <div class="gravatar">
 
	                         <img alt="gravatar" src="${h.gravatar_url(h.email(c.changeset.author),20)}"/>
 
	                     </div>
 
	                     <span>${h.person(c.changeset.author)}</span><br/>
 
	                     <span><a href="mailto:${h.email_or_none(c.changeset.author)}">${h.email_or_none(c.changeset.author)}</a></span><br/>
 
	                 </div>
 
	                 <div class="message">${h.wrap_paragraphs(c.changeset.message)}</div>
 
	                 <div class="message">${h.urlify_commit(h.wrap_paragraphs(c.changeset.message))}</div>
 
	             </div>
 
	             <div class="right">
 
		             <div class="changes">
 
                        % if len(c.changeset.affected_files) <= c.affected_files_cut_off:	             
 
		                 <span class="removed" title="${_('removed')}">${len(c.changeset.removed)}</span>
 
		                 <span class="changed" title="${_('changed')}">${len(c.changeset.changed)}</span>
 
		                 <span class="added" title="${_('added')}">${len(c.changeset.added)}</span>
 
	                    % else:
 
                         <span class="removed" title="${_('affected %s files') % len(c.changeset.affected_files)}">!</span>
 
                         <span class="changed" title="${_('affected %s files') % len(c.changeset.affected_files)}">!</span>
 
                         <span class="added"   title="${_('affected %s files') % len(c.changeset.affected_files)}">!</span>	                    
 
	                    % endif		                 
 
		             </div>                  
 
		                 
 
		            %if c.changeset.parents:
 
		             %for p_cs in reversed(c.changeset.parents):
 
		                 <div class="parent">${_('Parent')}
 
                     <span class="changeset_id">${p_cs.revision}:<span class="changeset_hash">${h.link_to(h.short_id(p_cs.raw_id),
 
		                     h.url('changeset_home',repo_name=c.repo_name,revision=p_cs.raw_id),title=p_cs.message)}</span></span>
 
		                 </div>
 
		             %endfor
 
                    %else: 
 
                        <div class="parent">${_('No parents')}</div>   
 
                    %endif		             
 
		         <span class="logtags">
 
                 %if len(c.changeset.parents)>1:
 
                 <span class="merge">${_('merge')}</span>
 
                 %endif
 
		             <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>
 
		             %for tag in c.changeset.tags:
 
		                 <span class="tagtag"  title="${'%s %s' % (_('tag'),tag)}">
 
		                 ${h.link_to(tag,h.url('files_home',repo_name=c.repo_name,revision=c.changeset.raw_id))}</span>
 
		             %endfor
 
		         </span>                                                                 
 
	                </div>              
 
	        </div>
 
	        <span>
 
	        ${_('%s files affected with %s additions and %s deletions:') % (len(c.changeset.affected_files),c.lines_added,c.lines_deleted)}
 
	        </span>
 
	        <div class="cs_files">
 
	                %for change,filenode,diff,cs1,cs2,stat in c.changes:
 
	                    <div class="cs_${change}">
 
                            <div class="node">
 
                            %if change != 'removed':
 
                                ${h.link_to(h.safe_unicode(filenode.path),c.anchor_url(filenode.changeset.raw_id,filenode.path))}
 
                                ${h.link_to(h.safe_unicode(filenode.path),c.anchor_url(filenode.changeset.raw_id,filenode.path)+"_target")}
 
                            %else:
 
                                ${h.link_to(h.safe_unicode(filenode.path),h.url.current(anchor=h.FID('',filenode.path)))}
 
                            %endif
 
                            </div>
 
		                    <div class="changes">${h.fancy_file_stats(stat)}</div>
 
	                    </div>
 
	                %endfor
 
	                % if c.cut_off:
 
	                  ${_('Changeset was too big and was cut off...')}
 
	                % endif
 
	        </div>         
 
	    </div>
 
	    
 
    </div>
 
    
 
    ## diff block    	
 
    <%namespace name="diff_block" file="/changeset/diff_block.html"/>
 
    ${diff_block.diff_block(c.changes)}
 
    
 
    ## template for inline comment form
 
    <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
 
    ${comment.comment_inline_form(c.changeset)}
 
    
 
    ${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(){
 
          
 
          YUE.on(YUQ('.show-inline-comments'),'change',function(e){
 
              var show = 'none';
 
              var target = e.currentTarget;
 
              console.log(target);
 
              if(target.checked){
 
                  var show = ''
 
              }
 
              console.log('aa')
 
              var boxid = YUD.getAttribute(target,'id_for');
 
              console.log(boxid);
 
              var comments = YUQ('#{0} .inline-comments'.format(boxid));
 
              console.log(comments)
 
              for(c in comments){ 
 
                 YUD.setStyle(comments[c],'display',show);
 
              }
 
              var btns = YUQ('#{0} .inline-comments-button'.format(boxid));
 
              for(c in btns){ 
 
                  YUD.setStyle(btns[c],'display',show);
 
               }              
 
          })
 
          
 
          YUE.on(YUQ('.line'),'click',function(e){
 
              var tr = e.currentTarget;
 
              injectInlineForm(tr);
 
          });
 
          
 
          // inject comments into they proper positions
 
          var file_comments = YUQ('.inline-comment-placeholder');
 
          
 
          for (f in file_comments){
 
              var box = file_comments[f];
 
              var inlines = box.children;
 
              for(var i=0; i<inlines.length; i++){
 
                  try{
 

	
 
                    var inline = inlines[i];
 
                    var lineno = YUD.getAttribute(inlines[i],'line');
 
                    var lineid = "{0}_{1}".format(YUD.getAttribute(inline,'target_id'),lineno);
 
                    var target_line = YUD.get(lineid);
 
                    
 
                    var add = createInlineAddButton(target_line.parentNode,'${_("add another comment")}');
 
                    YUD.insertAfter(add,target_line.parentNode);
 
                    
 
                    var comment = new YAHOO.util.Element(tableTr('inline-comments',inline.innerHTML))
 
                    YUD.insertAfter(comment,target_line.parentNode);
 
                  }catch(e){
 
                	  console.log(e);
 
                  }
 
              }
 
          }
 
      })
 
      
 
    </script>     
 
    
 
    </div>
 
</%def>
rhodecode/templates/changeset/changeset_range.html
Show inline comments
 
## -*- coding: utf-8 -*-
 
<%inherit file="/base/base.html"/>
 

	
 
<%def name="title()">
 
    ${c.repo_name} ${_('Changesets')} - r${c.cs_ranges[0].revision}:${h.short_id(c.cs_ranges[0].raw_id)} -> r${c.cs_ranges[-1].revision}:${h.short_id(c.cs_ranges[-1].raw_id)} - ${c.rhodecode_name}
 
</%def>
 

	
 
<%def name="breadcrumbs_links()">
 
    ${h.link_to(u'Home',h.url('/'))}
 
    &raquo;
 
    ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
 
    &raquo;
 
    ${_('Changesets')} - r${c.cs_ranges[0].revision}:${h.short_id(c.cs_ranges[0].raw_id)} -> r${c.cs_ranges[-1].revision}:${h.short_id(c.cs_ranges[-1].raw_id)}
 
</%def>
 

	
 
<%def name="page_nav()">
 
    ${self.menu('changelog')}     
 
</%def>
 

	
 
<%def name="main()">
 
<div class="box">
 
    <!-- box / title -->
 
    <div class="title">
 
        ${self.breadcrumbs()}
 
    </div>
 
    <div class="table">
 
		<div id="body" class="diffblock">
 
			<div class="code-header cv">
 
		        <h3 class="code-header-title">${_('Compare View')}</h3>		
 
                <div>
 
				${_('Changesets')} - r${c.cs_ranges[0].revision}:${h.short_id(c.cs_ranges[0].raw_id)} -> r${c.cs_ranges[-1].revision}:${h.short_id(c.cs_ranges[-1].raw_id)}
 
				</div>
 
			</div>
 
		</div>
 
	    <div id="changeset_compare_view_content">
 
			<div class="container">
 
			<table class="compare_view_commits noborder">
 
            %for cs in c.cs_ranges:
 
                <tr>
 
                <td><div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(h.email(cs.author),14)}"/></div></td>
 
                <td>${h.link_to('r%s:%s' % (cs.revision,h.short_id(cs.raw_id)),h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}</td>
 
                <td><div class="author">${h.person(cs.author)}</div></td>
 
                <td><span class="tooltip" title="${h.age(cs.date)}">${cs.date}</span></td>
 
                <td><div class="message">${h.link_to(h.wrap_paragraphs(cs.message),h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}</div></td>
 
                <td><div class="message">${h.urlify_commit(h.wrap_paragraphs(cs.message))}</div></td>
 
                </tr>
 
            %endfor
 
            </table>
 
	        </div>
 
	        <div style="font-size:1.1em;font-weight: bold;clear:both;padding-top:10px">${_('Files affected')}</div>
 
	        <div class="cs_files">
 
	               %for cs in c.cs_ranges:
 
	                   <div class="cur_cs">r${cs}</div>
 
	                %for change,filenode,diff,cs1,cs2,st in c.changes[cs.raw_id]:
 
	                    <div class="cs_${change}">${h.link_to(h.safe_unicode(filenode.path),h.url.current(anchor=h.FID(cs.raw_id,filenode.path)))}</div>
 
	                %endfor
 
	               %endfor 
 
	        </div>         
 
	    </div>
 
	    
 
    </div>
 
    <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
 
    <%namespace name="diff_block" file="/changeset/diff_block.html"/>
 
     %for cs in c.cs_ranges:
 
          ##${comment.comment_inline_form(cs)}    	
 
          ## diff block
 
          <h3 style="border:none;padding-top:8px;">${'r%s:%s' % (cs.revision,h.short_id(cs.raw_id))}</h3>
 
          ${diff_block.diff_block(c.changes[cs.raw_id])}
 
          ##${comment.comments(cs)}
 

	
 
     %endfor 
 
     <script type="text/javascript">
 

	
 
      YUE.onDOMReady(function(){
 
          
 
          YUE.on(YUQ('.diff-menu-activate'),'click',function(e){
 
              var act = e.currentTarget.nextElementSibling;
 
              
 
              if(YUD.hasClass(act,'active')){
 
                  YUD.removeClass(act,'active');
 
                  YUD.setStyle(act,'display','none');
 
              }else{
 
                  YUD.addClass(act,'active');
 
                  YUD.setStyle(act,'display','');
 
              }
 
          });
 
      })
 
    </script>    
 
    </div>
 
</%def>
 
\ No newline at end of file
rhodecode/templates/changeset/diff_block.html
Show inline comments
 
## -*- coding: utf-8 -*-
 
##usage:
 
## <%namespace name="diff_block" file="/changeset/diff_block.html"/>
 
## ${diff_block.diff_block(changes)}
 
##
 
<%def name="diff_block(changes)">
 

	
 
%for change,filenode,diff,cs1,cs2,stat in changes:
 
    %if change !='removed':
 
    <div id="${h.FID(filenode.changeset.raw_id,filenode.path)}" style="clear:both;height:90px;margin-top:-60px"></div>
 
    <div class="diffblock  margined comm">
 
    <div id="${h.FID(filenode.changeset.raw_id,filenode.path)}_target" style="clear:both;height:90px;margin-top:-60px"></div>
 
    <div id="${h.FID(filenode.changeset.raw_id,filenode.path)}" class="diffblock  margined comm">
 
        <div class="code-header">
 
            <div class="changeset_header">
 
                <div class="changeset_file">
 
                    ${h.link_to_if(change!='removed',h.safe_unicode(filenode.path),h.url('files_home',repo_name=c.repo_name,
 
                    revision=filenode.changeset.raw_id,f_path=h.safe_unicode(filenode.path)))}
 
                </div>
 
                <span class="diff-actions">
 
                  <a href="${h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(filenode.path),diff2=cs2,diff1=cs1,diff='diff',fulldiff=1)}" title="${_('diff')}"><img class="icon" src="${h.url('/images/icons/page_white_text.png')}"/></a>
 
                  <a href="${h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(filenode.path),diff2=cs2,diff1=cs1,diff='raw')}" title="${_('raw diff')}"><img class="icon" src="${h.url('/images/icons/page_white_text.png')}"/></a>
 
                  <a href="${h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(filenode.path),diff2=cs2,diff1=cs1,diff='download')}" title="${_('download diff')}"><img class="icon" src="${h.url('/images/icons/down_16.png')}"/></a>
 
                  ${c.ignorews_url(h.FID(filenode.changeset.raw_id,filenode.path))}
 
                  ${c.context_url(h.FID(filenode.changeset.raw_id,filenode.path))}
 
                </span>
 
                <span style="float:right;margin-top:-3px">
 
                  <label>
 
                  ${_('show inline comments')}
 
                  ${h.checkbox('',checked="checked",class_="show-inline-comments",id_for=h.FID(filenode.changeset.raw_id,filenode.path))}
 
                  </label>
 
                </span>
 
            </div>
 
        </div>
 
        <div class="code-body">
 
            <div class="full_f_path" path="${h.safe_unicode(filenode.path)}"></div>        
 
            ${diff|n}
 
        </div>
 
    </div>
 
    %endif
 
%endfor
 

	
 
</%def>
 
\ No newline at end of file
rhodecode/templates/shortlog/shortlog_data.html
Show inline comments
 
## -*- coding: utf-8 -*-
 
%if c.repo_changesets:
 
<table class="table_disp">
 
	<tr>
 
		<th class="left">${_('commit message')}</th>
 
	    <th class="left">${_('revision')}</th>	
 
        <th class="left">${_('commit message')}</th>
 
		<th class="left">${_('age')}</th>
 
		<th class="left">${_('author')}</th>
 
		<th class="left">${_('revision')}</th>
 
		<th class="left">${_('branch')}</th>
 
		<th class="left">${_('tags')}</th>
 
	</tr>
 
%for cnt,cs in enumerate(c.repo_changesets):
 
	<tr class="parity${cnt%2}">
 
        <td>
 
            <div><pre><a href="${h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id)}">r${cs.revision}:${h.short_id(cs.raw_id)}</a></pre></div>
 
        </td>  
 
        <td>
 
            ${h.link_to(h.truncate(cs.message,50),
 
            h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id),
 
            title=cs.message)}
 
        </td>
 
        <td><span class="tooltip" title="${cs.date}">
 
                      ${h.age(cs.date)}</span>
 
        </td>        	
 
		<td title="${cs.author}">${h.person(cs.author)}</td>
 
		<td><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>
 
			<span class="logtags">
 
				<span class="branchtag">${cs.branch}</span>
 
			</span>
 
		</td>
 
		<td>
 
			<span class="logtags">
 
				%for tag in cs.tags:
 
					<span class="tagtag">${tag}</span>
 
				%endfor
 
			</span>
 
		</td>
 
	</tr>
 
%endfor
 

	
 
</table>
 

	
 
<script type="text/javascript">
 
  YUE.onDOMReady(function(){
 
    YUE.delegate("shortlog_data","click",function(e, matchedEl, container){
 
        ypjax(e.target.href,"shortlog_data",function(){tooltip_activate();});
 
        YUE.preventDefault(e);
 
    },'.pager_link');	  
 
  });
 
</script>
 

	
 
<div class="pagination-wh pagination-left">
 
${c.repo_changesets.pager('$link_previous ~2~ $link_next')}
 
</div>
 
%else:
 

	
 
%if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
 
<h4>${_('Add or upload files directly via RhodeCode')}</h4>
 
<div style="margin: 20px 30px;">
 
  <div id="add_node_id" class="add_node">
 
      <a class="ui-btn" href="${h.url('files_add_home',repo_name=c.repo_name,revision=0,f_path='')}">${_('add new file')}</a>
 
  </div>
 
</div>
 
%endif
 
 
 

	
 
<h4>${_('Push new repo')}</h4>     
 
<pre>
 
    ${c.rhodecode_repo.alias} clone ${c.clone_repo_url}
 
    ${c.rhodecode_repo.alias} add README # add first file
 
    ${c.rhodecode_repo.alias} commit -m "Initial" # commit with message
 
    ${c.rhodecode_repo.alias} push # push changes back
 
</pre>
 
 
 
<h4>${_('Existing repository?')}</h4>
 
<pre>
 
    ${c.rhodecode_repo.alias} push ${c.clone_repo_url}
 
</pre>    
 
%endif
0 comments (0 inline, 0 general)