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*  ##
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
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
 
@@ -332,193 +332,211 @@ 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::
 

	
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*  ##
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  ##
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
 
@@ -4,256 +4,266 @@
 
    ~~~~~~~~~~~~~~~~~~~~~~~
 

	
 
    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
 

	
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
 
    """
 

	
 
@@ -635,121 +638,155 @@ 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
 
@@ -48,391 +48,399 @@ VALUE_IS_EMAIL_ADDRESS = lambda v: '@' i
 
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
 
@@ -59,198 +59,198 @@ 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()
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
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);
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)