##// END OF EJS Templates
Fixes for release 1.1.4...
marcink -
r1071:bdc438fb default
parent child Browse files
Show More
@@ -1,114 +1,126 b''
1 1
2 2 =================================================
3 3 Welcome to RhodeCode (RhodiumCode) documentation!
4 4 =================================================
5 5
6 6 ``RhodeCode`` (formerly hg-app) is Pylons framework based Mercurial repository
7 browser/management with build in push/pull server and full text search.
8 It works on http/https, has build in permission/authentication(+ldap) features
9 It's similar to github or bitbucket, but it's suppose to run as standalone
10 hosted application, it's open source and focuses more on restricted access to
11 repositories. It's powered by vcs_ library that me and Lukasz Balcerzak created
12 to handle many various version control systems.
7 browser/management tool with build in push/pull server and full text search.
8 It works on http/https, has build in permission/authentication system with
9 ability to auth via LDAP. It's similar in some parts to github or bitbucket,
10 but it's suppose to run as standalone hosted application, it's open source
11 and donation ware and focuses more on providing customized, self administered
12 interface for Mercurial(and soon GIT) repositories. It's powered by vcs_
13 library that me and Lukasz Balcerzak created to handle many various version
14 control systems.
13 15
14 16 RhodeCode uses `Semantic Versioning <http://semver.org/>`_
15 17
16 18 RhodeCode demo
17 19 --------------
18 20
19 21 http://hg.python-works.com
20 22
21 23 The default access is anonymous but You can login to administrative account
22 24 using those credentials
23 25
24 26 - username: demo
25 27 - password: demo
26 28
27 29 Source code
28 30 -----------
29 31
30 32 The most up to date sources can be obtained from my own RhodeCode instance
31 33 https://rhodecode.org
32 34
33 35 Rarely updated source code and issue tracker is available at bitbcuket
34 36 http://bitbucket.org/marcinkuzminski/rhodecode
35 37
36 38 Installation
37 39 ------------
38 40
39 41 Please visit http://packages.python.org/RhodeCode/installation.html
40 42
41 43
42 44 Features
43 45 --------
44 46
45 47 - Has it's own middleware to handle mercurial_ protocol request.
46 48 Each request can be logged and authenticated. Runs on threads unlikely to
47 49 hgweb. You can make multiple pulls/pushes simultaneous. Supports http/https
48 and ldap
50 and LDAP
49 51 - Full permissions (private/read/write/admin) and authentication per project.
50 52 One account for web interface and mercurial_ push/pull/clone operations.
51 53 - Mako templates let's you customize look and feel of application.
52 54 - Beautiful diffs, annotations and source codes all colored by pygments.
53 55 - Mercurial_ branch graph and yui-flot powered graphs with zooming and statistics
54 56 - Admin interface with user/permission management. Admin activity journal, logs
55 57 pulls, pushes, forks, registrations and other actions made by all users.
56 58 - Server side forks, it's possible to fork a project and hack it free without
57 59 breaking the main repository.
58 60 - Full text search powered by Whoosh on source codes, and file names.
59 61 Build in indexing daemons, with optional incremental index build
60 62 (no external search servers required all in one application)
61 63 - Setup project descriptions and info inside built in db for easy, non
62 64 file-system operations
63 - Inteligent cache with invalidation after push or project change, provides high
65 - Intelligent cache with invalidation after push or project change, provides high
64 66 performance and always up to date data.
65 67 - Rss / atom feeds, gravatar support, download sources as zip/tar/gz
66 68 - Async tasks for speed and performance using celery_ (works without them too)
67 69 - Backup scripts can do backup of whole app and send it over scp to desired
68 70 location
69 71 - Based on pylons / sqlalchemy / sqlite / whoosh / vcs
70 72
71 73
72 74 .. include:: ./docs/screenshots.rst
73 75
74 76
75 77 Incoming / Plans
76 78 ----------------
77 79
78 80 - project grouping
79 81 - User groups/teams
82 - ssh based authentication with server side key management
80 83 - code review (probably based on hg-review)
81 84 - full git_ support, with push/pull server (currently in beta tests)
82 85 - redmine integration
83 86 - public accessible activity feeds
84 87 - commit based build in wiki system
85 - clone points and cloning from remote repositories into rhodecode
86 (git_ and mercurial_)
88 - clone points and cloning from remote repositories into RhodeCode
87 89 - more statistics and graph (global annotation + some more statistics)
88 90 - other cools stuff that i can figure out (or You can help me figure out)
89 91
90 92 License
91 93 -------
92 94
93 ``rhodecode`` is released under GPL_ license.
95 ``RhodeCode`` is released under GPL_ license.
94 96
95 97
96 98 Mailing group Q&A
97 99 -----------------
98 100
99 101 join the `Google group <http://groups.google.com/group/rhodecode>`_
100 102
101 103 open an issue at `issue tracker <http://bitbucket.org/marcinkuzminski/rhodecode/issues>`_
102 104
103 105 join #rhodecode on FreeNode (irc.freenode.net)
104 106 or use http://webchat.freenode.net/?channels=rhodecode for web access to irc.
105 107
106 108 Online documentation
107 109 --------------------
108 110
109 111 Online documentation for current version is available at
110 112 http://packages.python.org/RhodeCode/.
111 113 You may also build documentation for yourself - go into ``docs/`` and run::
112 114
113 115 make html
114 116
117 .. _virtualenv: http://pypi.python.org/pypi/virtualenv
118 .. _python: http://www.python.org/
119 .. _django: http://www.djangoproject.com/
120 .. _mercurial: http://mercurial.selenic.com/
121 .. _subversion: http://subversion.tigris.org/
122 .. _git: http://git-scm.com/
123 .. _celery: http://celeryproject.org/
124 .. _Sphinx: http://sphinx.pocoo.org/
125 .. _GPL: http://www.gnu.org/licenses/gpl.html
126 .. _vcs: http://pypi.python.org/pypi/vcs No newline at end of file
@@ -1,177 +1,191 b''
1 1 .. _changelog:
2 2
3 3 Changelog
4 4 =========
5 5
6 1.1.4 (**2011-02-19**)
7 ======================
8
9 news
10 ----
11
12 fixes
13 -----
14
15 - fixed formencode import problem on settings page, that caused server crash
16 when that page was accessed as first after server start
17 - journal fixes
18 - fixed option to access repository just by entering http://server/<repo_name>
19
20
6 21 1.1.3 (**2011-02-16**)
7 22 ======================
8 23
9 24 news
10 25 ----
11 26
12 27 - implemented #102 allowing the '.' character in username
13 28 - added option to access repository just by entering http://server/<repo_name>
14 29 - celery task ignores result for better performance
15 30
16 31 fixes
17 32 -----
18 33
19 34 - fixed ehlo command and non auth mail servers on smtp_lib. Thanks to
20 35 apollo13 and Johan Walles
21 36 - small fixes in journal
22 37 - fixed problems with getting setting for celery from .ini files
23 38 - registration, password reset and login boxes share the same title as main
24 39 application now
25 40 - fixed #113: to high permissions to fork repository
26 41 - fixed problem with '[' chars in commit messages in journal
27 42 - removed issue with space inside renamed repository after deletion
28 43 - db transaction fixes when filesystem repository creation failed
29 44 - fixed #106 relation issues on databases different than sqlite
30 45 - fixed static files paths links to use of url() method
31 46
32
33 47 1.1.2 (**2011-01-12**)
34 48 ======================
35 49
36 50 news
37 51 ----
38 52
39 53
40 54 fixes
41 55 -----
42 56
43 57 - fixes #98 protection against float division of percentage stats
44 58 - fixed graph bug
45 59 - forced webhelpers version since it was making troubles during installation
46 60
47 61 1.1.1 (**2011-01-06**)
48 62 ======================
49 63
50 64 news
51 65 ----
52 66
53 67 - added force https option into ini files for easier https usage (no need to
54 68 set server headers with this options)
55 69 - small css updates
56 70
57 71 fixes
58 72 -----
59 73
60 74 - fixed #96 redirect loop on files view on repositories without changesets
61 75 - fixed #97 unicode string passed into server header in special cases (mod_wsgi)
62 76 and server crashed with errors
63 77 - fixed large tooltips problems on main page
64 78 - fixed #92 whoosh indexer is more error proof
65 79
66 80 1.1.0 (**2010-12-18**)
67 81 ======================
68 82
69 83 news
70 84 ----
71 85
72 86 - rewrite of internals for vcs >=0.1.10
73 87 - uses mercurial 1.7 with dotencode disabled for maintaining compatibility
74 88 with older clients
75 89 - anonymous access, authentication via ldap
76 90 - performance upgrade for cached repos list - each repository has it's own
77 91 cache that's invalidated when needed.
78 92 - performance upgrades on repositories with large amount of commits (20K+)
79 93 - main page quick filter for filtering repositories
80 94 - user dashboards with ability to follow chosen repositories actions
81 95 - sends email to admin on new user registration
82 96 - added cache/statistics reset options into repository settings
83 97 - more detailed action logger (based on hooks) with pushed changesets lists
84 98 and options to disable those hooks from admin panel
85 99 - introduced new enhanced changelog for merges that shows more accurate results
86 100 - new improved and faster code stats (based on pygments lexers mapping tables,
87 101 showing up to 10 trending sources for each repository. Additionally stats
88 102 can be disabled in repository settings.
89 103 - gui optimizations, fixed application width to 1024px
90 104 - added cut off (for large files/changesets) limit into config files
91 105 - whoosh, celeryd, upgrade moved to paster command
92 106 - other than sqlite database backends can be used
93 107
94 108 fixes
95 109 -----
96 110
97 111 - fixes #61 forked repo was showing only after cache expired
98 112 - fixes #76 no confirmation on user deletes
99 113 - fixes #66 Name field misspelled
100 114 - fixes #72 block user removal when he owns repositories
101 115 - fixes #69 added password confirmation fields
102 116 - fixes #87 RhodeCode crashes occasionally on updating repository owner
103 117 - fixes #82 broken annotations on files with more than 1 blank line at the end
104 118 - a lot of fixes and tweaks for file browser
105 119 - fixed detached session issues
106 120 - fixed when user had no repos he would see all repos listed in my account
107 121 - fixed ui() instance bug when global hgrc settings was loaded for server
108 122 instance and all hgrc options were merged with our db ui() object
109 123 - numerous small bugfixes
110 124
111 125 (special thanks for TkSoh for detailed feedback)
112 126
113 127
114 128 1.0.2 (**2010-11-12**)
115 129 ======================
116 130
117 131 news
118 132 ----
119 133
120 134 - tested under python2.7
121 135 - bumped sqlalchemy and celery versions
122 136
123 137 fixes
124 138 -----
125 139
126 140 - fixed #59 missing graph.js
127 141 - fixed repo_size crash when repository had broken symlinks
128 142 - fixed python2.5 crashes.
129 143
130 144
131 145 1.0.1 (**2010-11-10**)
132 146 ======================
133 147
134 148 news
135 149 ----
136 150
137 151 - small css updated
138 152
139 153 fixes
140 154 -----
141 155
142 156 - fixed #53 python2.5 incompatible enumerate calls
143 157 - fixed #52 disable mercurial extension for web
144 158 - fixed #51 deleting repositories don't delete it's dependent objects
145 159
146 160
147 161 1.0.0 (**2010-11-02**)
148 162 ======================
149 163
150 164 - security bugfix simplehg wasn't checking for permissions on commands
151 165 other than pull or push.
152 166 - fixed doubled messages after push or pull in admin journal
153 167 - templating and css corrections, fixed repo switcher on chrome, updated titles
154 168 - admin menu accessible from options menu on repository view
155 169 - permissions cached queries
156 170
157 171 1.0.0rc4 (**2010-10-12**)
158 172 ==========================
159 173
160 174 - fixed python2.5 missing simplejson imports (thanks to Jens BΓ€ckman)
161 175 - removed cache_manager settings from sqlalchemy meta
162 176 - added sqlalchemy cache settings to ini files
163 177 - validated password length and added second try of failure on paster setup-app
164 178 - fixed setup database destroy prompt even when there was no db
165 179
166 180
167 181 1.0.0rc3 (**2010-10-11**)
168 182 =========================
169 183
170 184 - fixed i18n during installation.
171 185
172 186 1.0.0rc2 (**2010-10-11**)
173 187 =========================
174 188
175 189 - Disabled dirsize in file browser, it's causing nasty bug when dir renames
176 190 occure. After vcs is fixed it'll be put back again.
177 191 - templating/css rewrites, optimized css. No newline at end of file
@@ -1,9 +1,17 b''
1 1 .. _contributing:
2 2
3 3 Contributing in RhodeCode
4 4 =========================
5 5
6 6 If You would like to contribute to RhodeCode, please contact me, any help is
7 7 greatly appreciated.
8 8
9 Preferable method Would be to fork RhodeCode repository from bitbucket
10 https://bitbucket.org/marcinkuzminski/rhodecode and then open a pull request.
11 This way it's easier for me to merge.
12
13 To run RhodeCode in a development version You always need to install tip
14 version of RhodeCode and VCS library.
15
16
9 17 Thank You.
@@ -1,99 +1,112 b''
1 1 .. _installation:
2 2
3 3 Installation
4 4 ============
5 5
6 6 ``RhodeCode`` is written entirely in Python, but in order to use it's full
7 7 potential there are some third-party requirements. When RhodeCode is used
8 8 together with celery You have to install some kind of message broker,
9 9 recommended one is rabbitmq_ to make the async tasks work.
10 10
11 11 Of course RhodeCode works in sync mode also, then You don't have to install
12 12 any third party apps. Celery_ will give You large speed improvement when using
13 13 many big repositories. If You plan to use it for 7 or 10 small repositories, it
14 14 will work just fine without celery running.
15 15
16 16 After You decide to Run it with celery make sure You run celeryd using paster
17 17 and message broker together with the application.
18 18
19 19 Install from Cheese Shop
20 20 ------------------------
21 21 Rhodecode requires python 2.x greater than version 2.5
22 22
23 23 Easiest way to install ``rhodecode`` is to run::
24 24
25 easy_install rhodecode
25 easy_install rhodecode
26 26
27 27 Or::
28 28
29 pip install rhodecode
29 pip install rhodecode
30 30
31 31 If you prefer to install manually simply grab latest release from
32 http://pypi.python.org/pypi/RhodeCode, decompres archive and run::
32 http://pypi.python.org/pypi/rhodecode, decompress archive and run::
33 33
34 python setup.py install
34 python setup.py install
35 35
36 36
37 37 Step by step installation example
38 38 ---------------------------------
39 39
40 40
41 41 - Assuming You have installed virtualenv_ create one using.
42 The `--no-site-packages` will make sure non of Your system libs are linked
43 with this virtualenv_
44 42
45 43 ::
46 44
47 virtualenv --no-site-packages /var/www/rhodecode-venv
45 virtualenv --no-site-packages /var/www/rhodecode-venv
46
47
48 .. note:: Using ``--no-site-packages`` when generating your
49 virtualenv is *very important*. This flag provides the necessary
50 isolation for running the set of packages required by
51 RhodeCode. If you do not specify ``--no-site-packages``,
52 it's possible that RhodeCode will not install properly into
53 the virtualenv, or, even if it does, may not run properly,
54 depending on the packages you've already got installed into your
55 Python's "main" site-packages dir.
56
48 57
49 58 - this will install new virtualenv_ into `/var/www/rhodecode-venv`.
50 59 - Activate the virtualenv_ by running
51 60
52 61 ::
53 62
54 source /var/www/rhodecode-venv/bin/activate
63 source /var/www/rhodecode-venv/bin/activate
64
65 .. note:: If you're on UNIX, *do not* use ``sudo`` to run the
66 ``virtualenv`` script. It's perfectly acceptable (and desirable)
67 to create a virtualenv as a normal user.
55 68
56 69 - Make a folder for rhodecode somewhere on the filesystem for example
57 70
58 71 ::
59 72
60 mkdir /var/www/rhodecode
73 mkdir /var/www/rhodecode
61 74
62 75
63 76 - Run this command to install rhodecode
64 77
65 78 ::
66 79
67 easy_install rhodecode
80 easy_install rhodecode
68 81
69 82 - this will install rhodecode together with pylons
70 83 and all other required python libraries
71 84
72 85 Requirements for Celery (optional)
73 86 ----------------------------------
74 87
75 88 .. note::
76 89 Installing message broker and using celery is optional, RhodeCode will
77 90 work without them perfectly fine.
78 91
79 92
80 93 **Message Broker**
81 94
82 95 - preferred is `RabbitMq <http://www.rabbitmq.com/>`_
83 96 - possible other is `Redis <http://code.google.com/p/redis/>`_
84 97
85 98 For installation instructions You can visit:
86 99 http://ask.github.com/celery/getting-started/index.html
87 100 It's very nice tutorial how to start celery_ with rabbitmq_
88 101
89 102
90 103 You can now proceed to :ref:`setup`
91 104 -----------------------------------
92 105
93 106
94 107
95 108 .. _virtualenv: http://pypi.python.org/pypi/virtualenv
96 109 .. _python: http://www.python.org/
97 110 .. _mercurial: http://mercurial.selenic.com/
98 111 .. _celery: http://celeryproject.org/
99 112 .. _rabbitmq: http://www.rabbitmq.com/ No newline at end of file
@@ -1,302 +1,358 b''
1 1 .. _setup:
2 2
3 3 Setup
4 4 =====
5 5
6 6
7 7 Setting up the application
8 8 --------------------------
9 9
10 First You'll ned to create RhodeCode config file. Run the following command
10 First You'll need to create RhodeCode config file. Run the following command
11 11 to do this
12 12
13 13 ::
14 14
15 15 paster make-config RhodeCode production.ini
16 16
17 17 - This will create `production.ini` config inside the directory
18 18 this config contains various settings for RhodeCode, e.g proxy port,
19 19 email settings, usage of static files, cache, celery settings and logging.
20 20
21 21
22 Next we need to create the database. I'll recommend to use sqlite (default)
23 or postgresql. Make sure You properly adjust the db url in the .ini file to use
24 other than the default sqlite database
22 25
23 Next we need to create the database.
24 26
25 27 ::
26 28
27 29 paster setup-app production.ini
28 30
29 31 - This command will create all needed tables and an admin account.
30 32 When asked for a path You can either use a new location of one with already
31 33 existing ones. RhodeCode will simply add all new found repositories to
32 34 it's database. Also make sure You specify correct path to repositories.
33 35 - Remember that the given path for mercurial_ repositories must be write
34 36 accessible for the application. It's very important since RhodeCode web
35 37 interface will work even without such an access but, when trying to do a
36 38 push it'll eventually fail with permission denied errors.
37 39
38 You are ready to use rhodecode, to run it simply execute
40 You are ready to use RhodeCode, to run it simply execute
39 41
40 42 ::
41 43
42 44 paster serve production.ini
43 45
44 46 - This command runs the RhodeCode server the app should be available at the
45 47 127.0.0.1:5000. This ip and port is configurable via the production.ini
46 48 file created in previous step
47 49 - Use admin account you created to login.
48 50 - Default permissions on each repository is read, and owner is admin. So
49 51 remember to update these if needed. In the admin panel You can toggle ldap,
50 52 anonymous, permissions settings. As well as edit more advanced options on
51 53 users and repositories
52 54
55 Using RhodeCode with SSH
56 ------------------------
57
58 RhodeCode repository structures are kept in directories with the same name
59 as the project, when using repository groups, each group is a a subdirectory.
60 This will allow You to use ssh for accessing repositories quite easy. There
61 are some exceptions when using ssh for accessing repositories.
62
63 You have to make sure that the webserver as well as the ssh users have unix
64 permission for directories. Secondly when using ssh rhodecode will not
65 authenticate those requests and permissions set by the web interface will not
66 work on the repositories accessed via ssh. There is a solution to this to use
67 auth hooks, that connects to rhodecode db, and runs check functions for
68 permissions.
69
70
71 if Your main directory (the same as set in RhodeCode settings) is for example
72 set for to **/home/hg** and repository You are using is `rhodecode`
73
74 The command runned should look like this::
75
76 hg clone ssh://user@server.com/home/hg/rhodecode
77
78 Using external tools such as mercurial server or using ssh key based auth is
79 fully supported.
53 80
54 81 Setting up Whoosh full text search
55 82 ----------------------------------
56 83
57 Index for whoosh can be build starting from version 1.1 using paster command
58 passing repo locations to index, as well as Your config file that stores
59 whoosh index files locations. There is possible to pass `-f` to the options
84 Starting from version 1.1 whoosh index can be build using paster command.
85 You have to specify the config file that stores location of index, and
86 location of repositories (`--repo-location`).
87
88 There is possible also to pass `-f` to the options
60 89 to enable full index rebuild. Without that indexing will run always in in
61 90 incremental mode.
62 91
63 ::
92 incremental mode::
64 93
65 paster make-index production.ini --repo-location=<location for repos>
94 paster make-index production.ini --repo-location=<location for repos>
95
66 96
67 for full index rebuild You can use
68 97
69 ::
98 for full index rebuild You can use::
70 99
71 paster make-index production.ini -f --repo-location=<location for repos>
100 paster make-index production.ini -f --repo-location=<location for repos>
72 101
73 102 - For full text search You can either put crontab entry for
74 103
75 This command can be run even from crontab in order to do periodical
76 index builds and keep Your index always up to date. An example entry might
77 look like this
104 In order to do periodical index builds and keep Your index always up to date.
105 It's recommended to do a crontab entry for incremental indexing.
106 An example entry might look like this
78 107
79 108 ::
80 109
81 110 /path/to/python/bin/paster /path/to/rhodecode/production.ini --repo-location=<location for repos>
82 111
83 When using incremental(default) mode whoosh will check last modification date
112 When using incremental (default) mode whoosh will check last modification date
84 113 of each file and add it to reindex if newer file is available. Also indexing
85 114 daemon checks for removed files and removes them from index.
86 115
87 116 Sometime You might want to rebuild index from scratch. You can do that using
88 117 the `-f` flag passed to paster command or, in admin panel You can check
89 118 `build from scratch` flag.
90 119
91 120
92 121 Setting up LDAP support
93 122 -----------------------
94 123
95 124 RhodeCode starting from version 1.1 supports ldap authentication. In order
96 to use ldap, You have to install python-ldap package. This package is available
125 to use LDAP, You have to install python-ldap_ package. This package is available
97 126 via pypi, so You can install it by running
98 127
99 128 ::
100 129
101 130 easy_install python-ldap
102 131
103 132 ::
104 133
105 134 pip install python-ldap
106 135
107 136 .. note::
108 137 python-ldap requires some certain libs on Your system, so before installing
109 138 it check that You have at least `openldap`, and `sasl` libraries.
110 139
111 140 ldap settings are located in admin->ldap section,
112 141
113 142 Here's a typical ldap setup::
114 143
115 144 Enable ldap = checked #controls if ldap access is enabled
116 145 Host = host.domain.org #actual ldap server to connect
117 146 Port = 389 or 689 for ldaps #ldap server ports
118 147 Enable LDAPS = unchecked #enable disable ldaps
119 148 Account = <account> #access for ldap server(if required)
120 149 Password = <password> #password for ldap server(if required)
121 150 Base DN = uid=%(user)s,CN=users,DC=host,DC=domain,DC=org
122 151
123 152
124 153 `Account` and `Password` are optional, and used for two-phase ldap
125 154 authentication so those are credentials to access Your ldap, if it doesn't
126 155 support anonymous search/user lookups.
127 156
128 157 Base DN must have %(user)s template inside, it's a placer where Your uid used
129 158 to login would go, it allows admins to specify not standard schema for uid
130 159 variable
131 160
132 161 If all data are entered correctly, and `python-ldap` is properly installed
133 162 Users should be granted to access RhodeCode wit ldap accounts. When
134 163 logging at the first time an special ldap account is created inside RhodeCode,
135 164 so You can control over permissions even on ldap users. If such user exists
136 165 already in RhodeCode database ldap user with the same username would be not
137 166 able to access RhodeCode.
138 167
139 168 If You have problems with ldap access and believe You entered correct
140 169 information check out the RhodeCode logs,any error messages sent from
141 170 ldap will be saved there.
142 171
143 172
144 173
145 174 Setting Up Celery
146 175 -----------------
147 176
148 177 Since version 1.1 celery is configured by the rhodecode ini configuration files
149 178 simply set use_celery=true in the ini file then add / change the configuration
150 179 variables inside the ini file.
151 180
152 181 Remember that the ini files uses format with '.' not with '_' like celery
153 182 so for example setting `BROKER_HOST` in celery means setting `broker.host` in
154 183 the config file.
155 184
156 185 In order to make start using celery run::
157 186
158 187 paster celeryd <configfile.ini>
159 188
189
160 190 .. note::
161 191 Make sure You run this command from same virtualenv, and with the same user
162 192 that rhodecode runs.
163 193
164 194 HTTPS support
165 195 -------------
166 196
167 197 There are two ways to enable https, first is to set HTTP_X_URL_SCHEME in
168 198 Your http server headers, than rhodecode will recognise this headers and make
169 199 proper https redirections, another way is to set `force_https = true`
170 200 in the ini cofiguration to force using https, no headers are needed than to
171 201 enable https
172 202
173 203
174 204 Nginx virtual host example
175 205 --------------------------
176 206
177 207 Sample config for nginx using proxy::
178 208
179 server {
180 listen 80;
181 server_name hg.myserver.com;
182 access_log /var/log/nginx/rhodecode.access.log;
183 error_log /var/log/nginx/rhodecode.error.log;
184 location / {
185 root /var/www/rhodecode/rhodecode/public/;
186 if (!-f $request_filename){
187 proxy_pass http://127.0.0.1:5000;
188 }
189 #this is important if You want to use https !!!
190 proxy_set_header X-Url-Scheme $scheme;
191 include /etc/nginx/proxy.conf;
192 }
193 }
209 server {
210 listen 80;
211 server_name hg.myserver.com;
212 access_log /var/log/nginx/rhodecode.access.log;
213 error_log /var/log/nginx/rhodecode.error.log;
214 location / {
215 root /var/www/rhodecode/rhodecode/public/;
216 if (!-f $request_filename){
217 proxy_pass http://127.0.0.1:5000;
218 }
219 #this is important if You want to use https !!!
220 proxy_set_header X-Url-Scheme $scheme;
221 include /etc/nginx/proxy.conf;
222 }
223 }
194 224
195 225 Here's the proxy.conf. It's tuned so it'll not timeout on long
196 226 pushes and also on large pushes::
197 227
198 228 proxy_redirect off;
199 229 proxy_set_header Host $host;
200 230 proxy_set_header X-Host $http_host;
201 231 proxy_set_header X-Real-IP $remote_addr;
202 232 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
203 233 proxy_set_header Proxy-host $proxy_host;
204 234 client_max_body_size 400m;
205 235 client_body_buffer_size 128k;
206 236 proxy_buffering off;
207 237 proxy_connect_timeout 3600;
208 238 proxy_send_timeout 3600;
209 239 proxy_read_timeout 3600;
210 proxy_buffer_size 8k;
211 proxy_buffers 8 32k;
240 proxy_buffer_size 16k;
241 proxy_buffers 4 16k;
212 242 proxy_busy_buffers_size 64k;
213 243 proxy_temp_file_write_size 64k;
214 244
215 245 Also when using root path with nginx You might set the static files to false
216 246 in production.ini file::
217 247
218 [app:main]
219 use = egg:rhodecode
220 full_stack = true
221 static_files = false
222 lang=en
223 cache_dir = %(here)s/data
248 [app:main]
249 use = egg:rhodecode
250 full_stack = true
251 static_files = false
252 lang=en
253 cache_dir = %(here)s/data
224 254
225 255 To not have the statics served by the application. And improve speed.
226 256
227 257
228 258 Apache virtual host example
229 259 ---------------------------
230 260
231 261 Sample config for apache using proxy::
232 262
233 263 <VirtualHost *:80>
234 264 ServerName hg.myserver.com
235 265 ServerAlias hg.myserver.com
236 266
237 267 <Proxy *>
238 268 Order allow,deny
239 269 Allow from all
240 270 </Proxy>
241 271
242 272 #important !
243 273 #Directive to properly generate url (clone url) for pylons
244 274 ProxyPreserveHost On
245 275
246 276 #rhodecode instance
247 277 ProxyPass / http://127.0.0.1:5000/
248 278 ProxyPassReverse / http://127.0.0.1:5000/
249 279
250 280 #to enable https use line below
251 281 #SetEnvIf X-Url-Scheme https HTTPS=1
252 282
253 283 </VirtualHost>
254 284
255 285
256 286 Additional tutorial
257 287 http://wiki.pylonshq.com/display/pylonscookbook/Apache+as+a+reverse+proxy+for+Pylons
258 288
259 289
290 Apache as subdirectory
291 ----------------------
292
293
294 Apache subdirectory part::
295
296 <Location /rhodecode>
297 ProxyPass http://127.0.0.1:59542/rhodecode
298 ProxyPassReverse http://127.0.0.1:59542/rhodecode
299 SetEnvIf X-Url-Scheme https HTTPS=1
300 </Location>
301
302 Besides the regular apache setup You'll need to add such part to .ini file::
303
304 filter-with = proxy-prefix
305
306 Add the following at the end of the .ini file::
307
308 [filter:proxy-prefix]
309 use = egg:PasteDeploy#prefix
310 prefix = /<someprefix>
311
312
260 313 Apache's example FCGI config
261 314 ----------------------------
262 315
263 316 TODO !
264 317
265 318 Other configuration files
266 319 -------------------------
267 320
268 321 Some example init.d script can be found here, for debian and gentoo:
269 322
270 323 https://rhodeocode.org/rhodecode/files/tip/init.d
271 324
272 325
273 326 Troubleshooting
274 327 ---------------
275 328
276 329 - missing static files ?
277 330
278 331 - make sure either to set the `static_files = true` in the .ini file or
279 332 double check the root path for Your http setup. It should point to
280 333 for example:
281 334 /home/my-virtual-python/lib/python2.6/site-packages/rhodecode/public
282 335
283 336 - can't install celery/rabbitmq
284 337
285 338 - don't worry RhodeCode works without them too. No extra setup required
286 339
287 340 - long lasting push timeouts ?
288 341
289 342 - make sure You set a longer timeouts in Your proxy/fcgi settings, timeouts
290 343 are caused by https server and not RhodeCode
291 344
292 345 - large pushes timeouts ?
293 346
294 347 - make sure You set a proper max_body_size for the http server
295 348
349 - Apache doesn't pass basicAuth on pull/push ?
296 350
351 - Make sure You added `WSGIPassAuthorization true`
297 352
298 353 .. _virtualenv: http://pypi.python.org/pypi/virtualenv
299 354 .. _python: http://www.python.org/
300 355 .. _mercurial: http://mercurial.selenic.com/
301 356 .. _celery: http://celeryproject.org/
302 .. _rabbitmq: http://www.rabbitmq.com/ No newline at end of file
357 .. _rabbitmq: http://www.rabbitmq.com/
358 .. _python-ldap: http://www.python-ldap.org/
@@ -1,51 +1,56 b''
1 1 .. _upgrade:
2 2
3 3 Upgrade
4 4 =======
5 5
6 6 Upgrade from Cheese Shop
7 7 ------------------------
8 8
9 9 Easiest way to upgrade ``rhodecode`` is to run::
10 10
11 11 easy_install -U rhodecode
12 12
13 13 Or::
14 14
15 15 pip install --upgrade rhodecode
16 16
17 17
18 18 Then make sure You run from the installation directory
19 19
20 20 ::
21 21
22 22 paster make-config RhodeCode production.ini
23 23
24 24 This will display any changes made from new version of RhodeCode To your
25 25 current config. And tries to do an automerge. It's always better to do a backup
26 26 of config file and recheck the content after merge.
27 27
28 .. note::
29 The next steps only apply to upgrading from non bugfix releases eg. from
30 any minor or major releases. Bugfix releases (eg. 1.1.2->1.1.3) will
31 not have any database schema changes or whoosh library updates
32
28 33 It's also good to rebuild the whoosh index since after upgrading the whoosh
29 34 version there could be introduced incompatible index changes.
30 35
31 36
32 37 The last step is to upgrade the database. To do this simply run
33 38
34 39 ::
35 40
36 paster upgrade-db production.ini
41 paster upgrade-db production.ini
37 42
38 43 This will upgrade schema, as well as update some default on the database,
39 44 always recheck the settings of the application, if there are no new options
40 45 that need to be set.
41 46
42 47 .. note::
43 48 Always perform a database backup before doing upgrade.
44 49
45 50
46 51
47 52 .. _virtualenv: http://pypi.python.org/pypi/virtualenv
48 53 .. _python: http://www.python.org/
49 54 .. _mercurial: http://mercurial.selenic.com/
50 55 .. _celery: http://celeryproject.org/
51 56 .. _rabbitmq: http://www.rabbitmq.com/ No newline at end of file
@@ -1,52 +1,52 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.__init__
4 4 ~~~~~~~~~~~~~~~~~~
5 5
6 6 RhodeCode, a web based repository management based on pylons
7 7 versioning implementation: http://semver.org/
8 8
9 9 :created_on: Apr 9, 2010
10 10 :author: marcink
11 11 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
12 12 :license: GPLv3, see COPYING for more details.
13 13 """
14 14 # This program is free software; you can redistribute it and/or
15 15 # modify it under the terms of the GNU General Public License
16 16 # as published by the Free Software Foundation; version 2
17 17 # of the License or (at your opinion) any later version of the license.
18 18 #
19 19 # This program is distributed in the hope that it will be useful,
20 20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 22 # GNU General Public License for more details.
23 23 #
24 24 # You should have received a copy of the GNU General Public License
25 25 # along with this program; if not, write to the Free Software
26 26 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
27 27 # MA 02110-1301, USA.
28 28
29 29
30 VERSION = (1, 1, 3)
30 VERSION = (1, 1, 4)
31 31 __version__ = '.'.join((str(each) for each in VERSION[:4]))
32 32 __dbversion__ = 2 #defines current db version for migrations
33 33
34 34 try:
35 35 from rhodecode.lib.utils import get_current_revision
36 36 _rev = get_current_revision()
37 37 except ImportError:
38 38 #this is needed when doing some setup.py operations
39 39 _rev = False
40 40
41 41 if len(VERSION) > 3 and _rev:
42 42 __version__ += ' [rev:%s]' % _rev[0]
43 43
44 44 def get_version():
45 45 """Returns shorter version (digit parts only) as string."""
46 46
47 47 return '.'.join((str(each) for each in VERSION[:3]))
48 48
49 49 BACKENDS = {
50 50 'hg': 'Mercurial repository',
51 51 #'git': 'Git repository',
52 52 }
@@ -1,212 +1,214 b''
1 1 """
2 2 Routes configuration
3 3
4 4 The more specific and detailed routes should be defined first so they
5 5 may take precedent over the more generic routes. For more information
6 6 refer to the routes manual at http://routes.groovie.org/docs/
7 7 """
8 8 from __future__ import with_statement
9 9 from routes import Mapper
10 10 from rhodecode.lib.utils import check_repo_fast as cr
11 11
12 12 def make_map(config):
13 13 """Create, configure and return the routes Mapper"""
14 14 map = Mapper(directory=config['pylons.paths']['controllers'],
15 15 always_scan=config['debug'])
16 16 map.minimization = False
17 17 map.explicit = False
18 18
19 19 def check_repo(environ, match_dict):
20 20 """
21 21 check for valid repository for proper 404 handling
22 22 :param environ:
23 23 :param match_dict:
24 24 """
25 25 repo_name = match_dict.get('repo_name')
26 26 return not cr(repo_name, config['base_path'])
27 27
28 28 # The ErrorController route (handles 404/500 error pages); it should
29 29 # likely stay at the top, ensuring it can always be resolved
30 30 map.connect('/error/{action}', controller='error')
31 31 map.connect('/error/{action}/{id}', controller='error')
32 32
33 33 #==========================================================================
34 34 # CUSTOM ROUTES HERE
35 35 #==========================================================================
36 36
37 37 #MAIN PAGE
38 38 map.connect('home', '/', controller='home', action='index')
39 39 map.connect('bugtracker', "http://bitbucket.org/marcinkuzminski/rhodecode/issues", _static=True)
40 40 map.connect('gpl_license', "http://www.gnu.org/licenses/gpl.html", _static=True)
41 41 #ADMIN REPOSITORY REST ROUTES
42 42 with map.submapper(path_prefix='/_admin', controller='admin/repos') as m:
43 43 m.connect("repos", "/repos",
44 44 action="create", conditions=dict(method=["POST"]))
45 45 m.connect("repos", "/repos",
46 46 action="index", conditions=dict(method=["GET"]))
47 47 m.connect("formatted_repos", "/repos.{format}",
48 48 action="index",
49 49 conditions=dict(method=["GET"]))
50 50 m.connect("new_repo", "/repos/new",
51 51 action="new", conditions=dict(method=["GET"]))
52 52 m.connect("formatted_new_repo", "/repos/new.{format}",
53 53 action="new", conditions=dict(method=["GET"]))
54 54 m.connect("/repos/{repo_name:.*}",
55 55 action="update", conditions=dict(method=["PUT"],
56 56 function=check_repo))
57 57 m.connect("/repos/{repo_name:.*}",
58 58 action="delete", conditions=dict(method=["DELETE"],
59 59 function=check_repo))
60 60 m.connect("edit_repo", "/repos/{repo_name:.*}/edit",
61 61 action="edit", conditions=dict(method=["GET"],
62 62 function=check_repo))
63 63 m.connect("formatted_edit_repo", "/repos/{repo_name:.*}.{format}/edit",
64 64 action="edit", conditions=dict(method=["GET"],
65 65 function=check_repo))
66 66 m.connect("repo", "/repos/{repo_name:.*}",
67 67 action="show", conditions=dict(method=["GET"],
68 68 function=check_repo))
69 69 m.connect("formatted_repo", "/repos/{repo_name:.*}.{format}",
70 70 action="show", conditions=dict(method=["GET"],
71 71 function=check_repo))
72 72 #ajax delete repo perm user
73 73 m.connect('delete_repo_user', "/repos_delete_user/{repo_name:.*}",
74 74 action="delete_perm_user", conditions=dict(method=["DELETE"],
75 75 function=check_repo))
76 76 #settings actions
77 77 m.connect('repo_stats', "/repos_stats/{repo_name:.*}",
78 78 action="repo_stats", conditions=dict(method=["DELETE"],
79 79 function=check_repo))
80 80 m.connect('repo_cache', "/repos_cache/{repo_name:.*}",
81 81 action="repo_cache", conditions=dict(method=["DELETE"],
82 82 function=check_repo))
83 83 #ADMIN USER REST ROUTES
84 84 map.resource('user', 'users', controller='admin/users', path_prefix='/_admin')
85 85
86 86 #ADMIN PERMISSIONS REST ROUTES
87 87 map.resource('permission', 'permissions', controller='admin/permissions', path_prefix='/_admin')
88 88
89 89
90 90 ##ADMIN LDAP SETTINGS
91 91 map.connect('ldap_settings', '/_admin/ldap', controller='admin/ldap_settings',
92 92 action='ldap_settings', conditions=dict(method=["POST"]))
93 93 map.connect('ldap_home', '/_admin/ldap', controller='admin/ldap_settings',)
94 94
95 95
96 96
97 97 #ADMIN SETTINGS REST ROUTES
98 98 with map.submapper(path_prefix='/_admin', controller='admin/settings') as m:
99 99 m.connect("admin_settings", "/settings",
100 100 action="create", conditions=dict(method=["POST"]))
101 101 m.connect("admin_settings", "/settings",
102 102 action="index", conditions=dict(method=["GET"]))
103 103 m.connect("formatted_admin_settings", "/settings.{format}",
104 104 action="index", conditions=dict(method=["GET"]))
105 105 m.connect("admin_new_setting", "/settings/new",
106 106 action="new", conditions=dict(method=["GET"]))
107 107 m.connect("formatted_admin_new_setting", "/settings/new.{format}",
108 108 action="new", conditions=dict(method=["GET"]))
109 109 m.connect("/settings/{setting_id}",
110 110 action="update", conditions=dict(method=["PUT"]))
111 111 m.connect("/settings/{setting_id}",
112 112 action="delete", conditions=dict(method=["DELETE"]))
113 113 m.connect("admin_edit_setting", "/settings/{setting_id}/edit",
114 114 action="edit", conditions=dict(method=["GET"]))
115 115 m.connect("formatted_admin_edit_setting", "/settings/{setting_id}.{format}/edit",
116 116 action="edit", conditions=dict(method=["GET"]))
117 117 m.connect("admin_setting", "/settings/{setting_id}",
118 118 action="show", conditions=dict(method=["GET"]))
119 119 m.connect("formatted_admin_setting", "/settings/{setting_id}.{format}",
120 120 action="show", conditions=dict(method=["GET"]))
121 121 m.connect("admin_settings_my_account", "/my_account",
122 122 action="my_account", conditions=dict(method=["GET"]))
123 123 m.connect("admin_settings_my_account_update", "/my_account_update",
124 124 action="my_account_update", conditions=dict(method=["PUT"]))
125 125 m.connect("admin_settings_create_repository", "/create_repository",
126 126 action="create_repository", conditions=dict(method=["GET"]))
127 127
128 128 #ADMIN MAIN PAGES
129 129 with map.submapper(path_prefix='/_admin', controller='admin/admin') as m:
130 130 m.connect('admin_home', '', action='index')#main page
131 131 m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
132 132 action='add_repo')
133 133
134 134
135 135 #USER JOURNAL
136 136 map.connect('journal', '/_admin/journal', controller='journal',)
137 137 map.connect('toggle_following', '/_admin/toggle_following', controller='journal',
138 138 action='toggle_following', conditions=dict(method=["POST"]))
139 139
140 140
141 141 #SEARCH
142 142 map.connect('search', '/_admin/search', controller='search',)
143 143 map.connect('search_repo', '/_admin/search/{search_repo:.*}', controller='search')
144 144
145 145 #LOGIN/LOGOUT/REGISTER/SIGN IN
146 146 map.connect('login_home', '/_admin/login', controller='login')
147 147 map.connect('logout_home', '/_admin/logout', controller='login', action='logout')
148 148 map.connect('register', '/_admin/register', controller='login', action='register')
149 149 map.connect('reset_password', '/_admin/password_reset', controller='login', action='password_reset')
150 150
151 151 #FEEDS
152 152 map.connect('rss_feed_home', '/{repo_name:.*}/feed/rss',
153 153 controller='feed', action='rss',
154 154 conditions=dict(function=check_repo))
155 155 map.connect('atom_feed_home', '/{repo_name:.*}/feed/atom',
156 156 controller='feed', action='atom',
157 157 conditions=dict(function=check_repo))
158 158
159 159
160 160 #REPOSITORY ROUTES
161 161 map.connect('changeset_home', '/{repo_name:.*}/changeset/{revision}',
162 162 controller='changeset', revision='tip',
163 163 conditions=dict(function=check_repo))
164 164 map.connect('raw_changeset_home', '/{repo_name:.*}/raw-changeset/{revision}',
165 165 controller='changeset', action='raw_changeset', revision='tip',
166 166 conditions=dict(function=check_repo))
167 map.connect('summary_home_', '/{repo_name:.*}',
168 controller='summary', conditions=dict(function=check_repo))
167 169 map.connect('summary_home', '/{repo_name:.*}/summary',
168 170 controller='summary', conditions=dict(function=check_repo))
169 171 map.connect('shortlog_home', '/{repo_name:.*}/shortlog',
170 172 controller='shortlog', conditions=dict(function=check_repo))
171 173 map.connect('branches_home', '/{repo_name:.*}/branches',
172 174 controller='branches', conditions=dict(function=check_repo))
173 175 map.connect('tags_home', '/{repo_name:.*}/tags',
174 176 controller='tags', conditions=dict(function=check_repo))
175 177 map.connect('changelog_home', '/{repo_name:.*}/changelog',
176 178 controller='changelog', conditions=dict(function=check_repo))
177 179 map.connect('files_home', '/{repo_name:.*}/files/{revision}/{f_path:.*}',
178 180 controller='files', revision='tip', f_path='',
179 181 conditions=dict(function=check_repo))
180 182 map.connect('files_diff_home', '/{repo_name:.*}/diff/{f_path:.*}',
181 183 controller='files', action='diff', revision='tip', f_path='',
182 184 conditions=dict(function=check_repo))
183 185 map.connect('files_rawfile_home', '/{repo_name:.*}/rawfile/{revision}/{f_path:.*}',
184 186 controller='files', action='rawfile', revision='tip', f_path='',
185 187 conditions=dict(function=check_repo))
186 188 map.connect('files_raw_home', '/{repo_name:.*}/raw/{revision}/{f_path:.*}',
187 189 controller='files', action='raw', revision='tip', f_path='',
188 190 conditions=dict(function=check_repo))
189 191 map.connect('files_annotate_home', '/{repo_name:.*}/annotate/{revision}/{f_path:.*}',
190 192 controller='files', action='annotate', revision='tip', f_path='',
191 193 conditions=dict(function=check_repo))
192 194 map.connect('files_archive_home', '/{repo_name:.*}/archive/{revision}/{fileformat}',
193 195 controller='files', action='archivefile', revision='tip',
194 196 conditions=dict(function=check_repo))
195 197 map.connect('repo_settings_delete', '/{repo_name:.*}/settings',
196 198 controller='settings', action="delete",
197 199 conditions=dict(method=["DELETE"], function=check_repo))
198 200 map.connect('repo_settings_update', '/{repo_name:.*}/settings',
199 201 controller='settings', action="update",
200 202 conditions=dict(method=["PUT"], function=check_repo))
201 203 map.connect('repo_settings_home', '/{repo_name:.*}/settings',
202 204 controller='settings', action='index',
203 205 conditions=dict(function=check_repo))
204 206
205 207 map.connect('repo_fork_create_home', '/{repo_name:.*}/fork',
206 208 controller='settings', action='fork_create',
207 209 conditions=dict(function=check_repo, method=["POST"]))
208 210 map.connect('repo_fork_home', '/{repo_name:.*}/fork',
209 211 controller='settings', action='fork',
210 212 conditions=dict(function=check_repo))
211 213
212 214 return map
@@ -1,189 +1,190 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.settings
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Settings controller for rhodecode
7 7
8 8 :created_on: Jun 30, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software; you can redistribute it and/or
14 14 # modify it under the terms of the GNU General Public License
15 15 # as published by the Free Software Foundation; version 2
16 16 # of the License or (at your opinion) any later version of the license.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program; if not, write to the Free Software
25 25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 26 # MA 02110-1301, USA.
27 27
28 28 import logging
29 29 import traceback
30 import formencode
30 31
31 import formencode
32 from formencode import htmlfill
32 33
33 34 from pylons import tmpl_context as c, request, url
34 35 from pylons.controllers.util import redirect
35 36 from pylons.i18n.translation import _
36 37
37 38 import rhodecode.lib.helpers as h
38 39 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAllDecorator, \
39 40 HasRepoPermissionAnyDecorator, NotAnonymous
40 41 from rhodecode.lib.base import BaseController, render
41 42 from rhodecode.lib.utils import invalidate_cache, action_logger
42 43 from rhodecode.model.forms import RepoSettingsForm, RepoForkForm
43 44 from rhodecode.model.repo import RepoModel
44 45
45 46 log = logging.getLogger(__name__)
46 47
47 48 class SettingsController(BaseController):
48 49
49 50 @LoginRequired()
50 51 def __before__(self):
51 52 super(SettingsController, self).__before__()
52 53
53 54 @HasRepoPermissionAllDecorator('repository.admin')
54 55 def index(self, repo_name):
55 56 repo_model = RepoModel()
56 57 c.repo_info = repo = repo_model.get_by_repo_name(repo_name)
57 58 if not repo:
58 59 h.flash(_('%s repository is not mapped to db perhaps'
59 60 ' it was created or renamed from the file system'
60 61 ' please run the application again'
61 62 ' in order to rescan repositories') % repo_name,
62 63 category='error')
63 64
64 65 return redirect(url('home'))
65 66 defaults = c.repo_info.get_dict()
66 67 defaults.update({'user':c.repo_info.user.username})
67 68 c.users_array = repo_model.get_users_js()
68 69
69 70 for p in c.repo_info.repo_to_perm:
70 71 defaults.update({'perm_%s' % p.user.username:
71 72 p.permission.permission_name})
72 73
73 return formencode.htmlfill.render(
74 return htmlfill.render(
74 75 render('settings/repo_settings.html'),
75 76 defaults=defaults,
76 77 encoding="UTF-8",
77 78 force_defaults=False
78 79 )
79 80
80 81 @HasRepoPermissionAllDecorator('repository.admin')
81 82 def update(self, repo_name):
82 83 repo_model = RepoModel()
83 84 changed_name = repo_name
84 85 _form = RepoSettingsForm(edit=True, old_data={'repo_name':repo_name})()
85 86 try:
86 87 form_result = _form.to_python(dict(request.POST))
87 88 repo_model.update(repo_name, form_result)
88 89 invalidate_cache('get_repo_cached_%s' % repo_name)
89 90 h.flash(_('Repository %s updated successfully' % repo_name),
90 91 category='success')
91 92 changed_name = form_result['repo_name']
92 93 action_logger(self.rhodecode_user, 'user_updated_repo',
93 94 changed_name, '', self.sa)
94 95 except formencode.Invalid, errors:
95 96 c.repo_info = repo_model.get_by_repo_name(repo_name)
96 97 c.users_array = repo_model.get_users_js()
97 98 errors.value.update({'user':c.repo_info.user.username})
98 return formencode.htmlfill.render(
99 return htmlfill.render(
99 100 render('settings/repo_settings.html'),
100 101 defaults=errors.value,
101 102 errors=errors.error_dict or {},
102 103 prefix_error=False,
103 104 encoding="UTF-8")
104 105 except Exception:
105 106 log.error(traceback.format_exc())
106 107 h.flash(_('error occurred during update of repository %s') \
107 108 % repo_name, category='error')
108 109
109 110 return redirect(url('repo_settings_home', repo_name=changed_name))
110 111
111 112
112 113 @HasRepoPermissionAllDecorator('repository.admin')
113 114 def delete(self, repo_name):
114 115 """DELETE /repos/repo_name: Delete an existing item"""
115 116 # Forms posted to this method should contain a hidden field:
116 117 # <input type="hidden" name="_method" value="DELETE" />
117 118 # Or using helpers:
118 119 # h.form(url('repo_settings_delete', repo_name=ID),
119 120 # method='delete')
120 121 # url('repo_settings_delete', repo_name=ID)
121 122
122 123 repo_model = RepoModel()
123 124 repo = repo_model.get_by_repo_name(repo_name)
124 125 if not repo:
125 126 h.flash(_('%s repository is not mapped to db perhaps'
126 127 ' it was moved or renamed from the filesystem'
127 128 ' please run the application again'
128 129 ' in order to rescan repositories') % repo_name,
129 130 category='error')
130 131
131 132 return redirect(url('home'))
132 133 try:
133 134 action_logger(self.rhodecode_user, 'user_deleted_repo',
134 135 repo_name, '', self.sa)
135 136 repo_model.delete(repo)
136 137 invalidate_cache('get_repo_cached_%s' % repo_name)
137 138 h.flash(_('deleted repository %s') % repo_name, category='success')
138 139 except Exception:
139 140 h.flash(_('An error occurred during deletion of %s') % repo_name,
140 141 category='error')
141 142
142 143 return redirect(url('home'))
143 144
144 145 @NotAnonymous()
145 146 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
146 147 'repository.admin')
147 148 def fork(self, repo_name):
148 149 repo_model = RepoModel()
149 150 c.repo_info = repo = repo_model.get_by_repo_name(repo_name)
150 151 if not repo:
151 152 h.flash(_('%s repository is not mapped to db perhaps'
152 153 ' it was created or renamed from the file system'
153 154 ' please run the application again'
154 155 ' in order to rescan repositories') % repo_name,
155 156 category='error')
156 157
157 158 return redirect(url('home'))
158 159
159 160 return render('settings/repo_fork.html')
160 161
161 162 @NotAnonymous()
162 163 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
163 164 'repository.admin')
164 165 def fork_create(self, repo_name):
165 166 repo_model = RepoModel()
166 167 c.repo_info = repo_model.get_by_repo_name(repo_name)
167 168 _form = RepoForkForm(old_data={'repo_type':c.repo_info.repo_type})()
168 169 form_result = {}
169 170 try:
170 171 form_result = _form.to_python(dict(request.POST))
171 172 form_result.update({'repo_name':repo_name})
172 173 repo_model.create_fork(form_result, c.rhodecode_user)
173 174 h.flash(_('forked %s repository as %s') \
174 175 % (repo_name, form_result['fork_name']),
175 176 category='success')
176 177 action_logger(self.rhodecode_user,
177 178 'user_forked_repo:%s' % form_result['fork_name'],
178 179 repo_name, '', self.sa)
179 180 except formencode.Invalid, errors:
180 181 c.new_repo = errors.value['fork_name']
181 182 r = render('settings/repo_fork.html')
182 183
183 return formencode.htmlfill.render(
184 return htmlfill.render(
184 185 r,
185 186 defaults=errors.value,
186 187 errors=errors.error_dict or {},
187 188 prefix_error=False,
188 189 encoding="UTF-8")
189 190 return redirect(url('home'))
@@ -1,557 +1,555 b''
1 1 """Helper functions
2 2
3 3 Consists of functions to typically be used within templates, but also
4 4 available to Controllers. This module is available to both as 'h'.
5 5 """
6 6 import random
7 7 import hashlib
8 8 from pygments.formatters import HtmlFormatter
9 9 from pygments import highlight as code_highlight
10 10 from pylons import url, app_globals as g
11 11 from pylons.i18n.translation import _, ungettext
12 12 from vcs.utils.annotate import annotate_highlight
13 13 from webhelpers.html import literal, HTML, escape
14 14 from webhelpers.html.tools import *
15 15 from webhelpers.html.builder import make_tag
16 16 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
17 17 end_form, file, form, hidden, image, javascript_link, link_to, link_to_if, \
18 18 link_to_unless, ol, required_legend, select, stylesheet_link, submit, text, \
19 19 password, textarea, title, ul, xml_declaration, radio
20 20 from webhelpers.html.tools import auto_link, button_to, highlight, js_obfuscate, \
21 21 mail_to, strip_links, strip_tags, tag_re
22 22 from webhelpers.number import format_byte_size, format_bit_size
23 23 from webhelpers.pylonslib import Flash as _Flash
24 24 from webhelpers.pylonslib.secure_form import secure_form
25 25 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
26 26 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
27 27 replace_whitespace, urlify, truncate, wrap_paragraphs
28 28 from webhelpers.date import time_ago_in_words
29 29
30 30 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
31 31 convert_boolean_attrs, NotGiven
32 32
33 33 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
34 34 _set_input_attrs(attrs, type, name, value)
35 35 _set_id_attr(attrs, id, name)
36 36 convert_boolean_attrs(attrs, ["disabled"])
37 37 return HTML.input(**attrs)
38 38
39 39 reset = _reset
40 40
41 41
42 42 def get_token():
43 43 """Return the current authentication token, creating one if one doesn't
44 44 already exist.
45 45 """
46 46 token_key = "_authentication_token"
47 47 from pylons import session
48 48 if not token_key in session:
49 49 try:
50 50 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
51 51 except AttributeError: # Python < 2.4
52 52 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
53 53 session[token_key] = token
54 54 if hasattr(session, 'save'):
55 55 session.save()
56 56 return session[token_key]
57 57
58 58
59 59 #Custom helpers here :)
60 60 class _Link(object):
61 61 '''
62 62 Make a url based on label and url with help of url_for
63 63 :param label:name of link if not defined url is used
64 64 :param url: the url for link
65 65 '''
66 66
67 67 def __call__(self, label='', *url_, **urlargs):
68 68 if label is None or '':
69 69 label = url
70 70 link_fn = link_to(label, url(*url_, **urlargs))
71 71 return link_fn
72 72
73 73 link = _Link()
74 74
75 75 class _GetError(object):
76 76
77 77 def __call__(self, field_name, form_errors):
78 78 tmpl = """<span class="error_msg">%s</span>"""
79 79 if form_errors and form_errors.has_key(field_name):
80 80 return literal(tmpl % form_errors.get(field_name))
81 81
82 82 get_error = _GetError()
83 83
84 84 def recursive_replace(str, replace=' '):
85 85 """
86 86 Recursive replace of given sign to just one instance
87 87 :param str: given string
88 88 :param replace:char to find and replace multiple instances
89 89
90 90 Examples::
91 91 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
92 92 'Mighty-Mighty-Bo-sstones'
93 93 """
94 94
95 95 if str.find(replace * 2) == -1:
96 96 return str
97 97 else:
98 98 str = str.replace(replace * 2, replace)
99 99 return recursive_replace(str, replace)
100 100
101 101 class _ToolTip(object):
102 102
103 103 def __call__(self, tooltip_title, trim_at=50):
104 104 """
105 105 Special function just to wrap our text into nice formatted autowrapped
106 106 text
107 107 :param tooltip_title:
108 108 """
109 109
110 110 return wrap_paragraphs(escape(tooltip_title), trim_at)\
111 111 .replace('\n', '<br/>')
112 112
113 113 def activate(self):
114 114 """
115 115 Adds tooltip mechanism to the given Html all tooltips have to have
116 116 set class tooltip and set attribute tooltip_title.
117 117 Then a tooltip will be generated based on that
118 118 All with yui js tooltip
119 119 """
120 120
121 121 js = '''
122 122 YAHOO.util.Event.onDOMReady(function(){
123 123 function toolTipsId(){
124 124 var ids = [];
125 125 var tts = YAHOO.util.Dom.getElementsByClassName('tooltip');
126 126
127 127 for (var i = 0; i < tts.length; i++) {
128 128 //if element doesn't not have and id autogenerate one for tooltip
129 129
130 130 if (!tts[i].id){
131 131 tts[i].id='tt'+i*100;
132 132 }
133 133 ids.push(tts[i].id);
134 134 }
135 135 return ids
136 136 };
137 137 var myToolTips = new YAHOO.widget.Tooltip("tooltip", {
138 138 context: toolTipsId(),
139 139 monitorresize:false,
140 140 xyoffset :[0,0],
141 141 autodismissdelay:300000,
142 142 hidedelay:5,
143 143 showdelay:20,
144 144 });
145 145
146 146 //Mouse Over event disabled for new repositories since they don't
147 147 //have last commit message
148 148 myToolTips.contextMouseOverEvent.subscribe(
149 149 function(type, args) {
150 150 var context = args[0];
151 151 var txt = context.getAttribute('tooltip_title');
152 152 if(txt){
153 153 return true;
154 154 }
155 155 else{
156 156 return false;
157 157 }
158 158 });
159 159
160 160
161 161 // Set the text for the tooltip just before we display it. Lazy method
162 162 myToolTips.contextTriggerEvent.subscribe(
163 163 function(type, args) {
164 164
165 165
166 166 var context = args[0];
167 167
168 168 var txt = context.getAttribute('tooltip_title');
169 169 this.cfg.setProperty("text", txt);
170 170
171 171
172 172 // positioning of tooltip
173 173 var tt_w = this.element.clientWidth;
174 174 var tt_h = this.element.clientHeight;
175 175
176 176 var context_w = context.offsetWidth;
177 177 var context_h = context.offsetHeight;
178 178
179 179 var pos_x = YAHOO.util.Dom.getX(context);
180 180 var pos_y = YAHOO.util.Dom.getY(context);
181 181
182 182 var display_strategy = 'top';
183 183 var xy_pos = [0,0];
184 184 switch (display_strategy){
185 185
186 186 case 'top':
187 187 var cur_x = (pos_x+context_w/2)-(tt_w/2);
188 188 var cur_y = (pos_y-tt_h-4);
189 189 xy_pos = [cur_x,cur_y];
190 190 break;
191 191 case 'bottom':
192 192 var cur_x = (pos_x+context_w/2)-(tt_w/2);
193 193 var cur_y = pos_y+context_h+4;
194 194 xy_pos = [cur_x,cur_y];
195 195 break;
196 196 case 'left':
197 197 var cur_x = (pos_x-tt_w-4);
198 198 var cur_y = pos_y-((tt_h/2)-context_h/2);
199 199 xy_pos = [cur_x,cur_y];
200 200 break;
201 201 case 'right':
202 202 var cur_x = (pos_x+context_w+4);
203 203 var cur_y = pos_y-((tt_h/2)-context_h/2);
204 204 xy_pos = [cur_x,cur_y];
205 205 break;
206 206 default:
207 207 var cur_x = (pos_x+context_w/2)-(tt_w/2);
208 208 var cur_y = pos_y-tt_h-4;
209 209 xy_pos = [cur_x,cur_y];
210 210 break;
211 211
212 212 }
213 213
214 214 this.cfg.setProperty("xy",xy_pos);
215 215
216 216 });
217 217
218 218 //Mouse out
219 219 myToolTips.contextMouseOutEvent.subscribe(
220 220 function(type, args) {
221 221 var context = args[0];
222 222
223 223 });
224 224 });
225 225 '''
226 226 return literal(js)
227 227
228 228 tooltip = _ToolTip()
229 229
230 230 class _FilesBreadCrumbs(object):
231 231
232 232 def __call__(self, repo_name, rev, paths):
233 233 url_l = [link_to(repo_name, url('files_home',
234 234 repo_name=repo_name,
235 235 revision=rev, f_path=''))]
236 236 paths_l = paths.split('/')
237 237
238 238 for cnt, p in enumerate(paths_l):
239 239 if p != '':
240 240 url_l.append(link_to(p, url('files_home',
241 241 repo_name=repo_name,
242 242 revision=rev,
243 243 f_path='/'.join(paths_l[:cnt + 1]))))
244 244
245 245 return literal('/'.join(url_l))
246 246
247 247 files_breadcrumbs = _FilesBreadCrumbs()
248 248 class CodeHtmlFormatter(HtmlFormatter):
249 249
250 250 def wrap(self, source, outfile):
251 251 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
252 252
253 253 def _wrap_code(self, source):
254 254 for cnt, it in enumerate(source):
255 255 i, t = it
256 256 t = '<div id="#S-%s">%s</div>' % (cnt + 1, t)
257 257 yield i, t
258 258 def pygmentize(filenode, **kwargs):
259 259 """
260 260 pygmentize function using pygments
261 261 :param filenode:
262 262 """
263 263 return literal(code_highlight(filenode.content,
264 264 filenode.lexer, CodeHtmlFormatter(**kwargs)))
265 265
266 266 def pygmentize_annotation(filenode, **kwargs):
267 267 """
268 268 pygmentize function for annotation
269 269 :param filenode:
270 270 """
271 271
272 272 color_dict = {}
273 273 def gen_color():
274 274 """generator for getting 10k of evenly distibuted colors using hsv color
275 275 and golden ratio.
276 276 """
277 277 import colorsys
278 278 n = 10000
279 279 golden_ratio = 0.618033988749895
280 280 h = 0.22717784590367374
281 281 #generate 10k nice web friendly colors in the same order
282 282 for c in xrange(n):
283 283 h += golden_ratio
284 284 h %= 1
285 285 HSV_tuple = [h, 0.95, 0.95]
286 286 RGB_tuple = colorsys.hsv_to_rgb(*HSV_tuple)
287 287 yield map(lambda x:str(int(x * 256)), RGB_tuple)
288 288
289 289 cgenerator = gen_color()
290 290
291 291 def get_color_string(cs):
292 292 if color_dict.has_key(cs):
293 293 col = color_dict[cs]
294 294 else:
295 295 col = color_dict[cs] = cgenerator.next()
296 296 return "color: rgb(%s)! important;" % (', '.join(col))
297 297
298 298 def url_func(changeset):
299 299 tooltip_html = "<div style='font-size:0.8em'><b>Author:</b>" + \
300 300 " %s<br/><b>Date:</b> %s</b><br/><b>Message:</b> %s<br/></div>"
301 301
302 302 tooltip_html = tooltip_html % (changeset.author,
303 303 changeset.date,
304 304 tooltip(changeset.message))
305 305 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
306 306 short_id(changeset.raw_id))
307 307 uri = link_to(
308 308 lnk_format,
309 309 url('changeset_home', repo_name=changeset.repository.name,
310 310 revision=changeset.raw_id),
311 311 style=get_color_string(changeset.raw_id),
312 312 class_='tooltip',
313 313 tooltip_title=tooltip_html
314 314 )
315 315
316 316 uri += '\n'
317 317 return uri
318 318 return literal(annotate_highlight(filenode, url_func, **kwargs))
319 319
320 320 def repo_name_slug(value):
321 321 """Return slug of name of repository
322 322 This function is called on each creation/modification
323 323 of repository to prevent bad names in repo
324 324 """
325 325 slug = remove_formatting(value)
326 326 slug = strip_tags(slug)
327 327
328 328 for c in """=[]\;'"<>,/~!@#$%^&*()+{}|: """:
329 329 slug = slug.replace(c, '-')
330 330 slug = recursive_replace(slug, '-')
331 331 slug = collapse(slug, '-')
332 332 return slug
333 333
334 334 def get_changeset_safe(repo, rev):
335 335 from vcs.backends.base import BaseRepository
336 336 from vcs.exceptions import RepositoryError
337 337 if not isinstance(repo, BaseRepository):
338 338 raise Exception('You must pass an Repository '
339 339 'object as first argument got %s', type(repo))
340 340
341 341 try:
342 342 cs = repo.get_changeset(rev)
343 343 except RepositoryError:
344 344 from rhodecode.lib.utils import EmptyChangeset
345 345 cs = EmptyChangeset()
346 346 return cs
347 347
348 348
349 349 flash = _Flash()
350 350
351 351
352 352 #==============================================================================
353 353 # MERCURIAL FILTERS available via h.
354 354 #==============================================================================
355 355 from mercurial import util
356 356 from mercurial.templatefilters import person as _person
357 357
358 358
359 359
360 360 def _age(curdate):
361 361 """turns a datetime into an age string."""
362 362
363 363 if not curdate:
364 364 return ''
365 365
366 366 from datetime import timedelta, datetime
367 367
368 368 agescales = [("year", 3600 * 24 * 365),
369 369 ("month", 3600 * 24 * 30),
370 370 ("day", 3600 * 24),
371 371 ("hour", 3600),
372 372 ("minute", 60),
373 373 ("second", 1), ]
374 374
375 375 age = datetime.now() - curdate
376 376 age_seconds = (age.days * agescales[2][1]) + age.seconds
377 377 pos = 1
378 378 for scale in agescales:
379 379 if scale[1] <= age_seconds:
380 380 if pos == 6:pos = 5
381 381 return time_ago_in_words(curdate, agescales[pos][0]) + ' ' + _('ago')
382 382 pos += 1
383 383
384 384 return _('just now')
385 385
386 386 age = lambda x:_age(x)
387 387 capitalize = lambda x: x.capitalize()
388 388 email = util.email
389 389 email_or_none = lambda x: util.email(x) if util.email(x) != x else None
390 390 person = lambda x: _person(x)
391 391 short_id = lambda x: x[:12]
392 392
393 393
394 394 def bool2icon(value):
395 395 """
396 396 Returns True/False values represented as small html image of true/false
397 397 icons
398 398 :param value: bool value
399 399 """
400 400
401 401 if value is True:
402 402 return HTML.tag('img', src=url("/images/icons/accept.png"), alt=_('True'))
403 403
404 404 if value is False:
405 405 return HTML.tag('img', src=url("/images/icons/cancel.png"), alt=_('False'))
406 406
407 407 return value
408 408
409 409
410 410 def action_parser(user_log):
411 411 """
412 412 This helper will map the specified string action into translated
413 413 fancy names with icons and links
414 414
415 415 @param action:
416 416 """
417 417 action = user_log.action
418 418 action_params = ' '
419 419
420 420 x = action.split(':')
421 421
422 422 if len(x) > 1:
423 423 action, action_params = x
424 424
425 425 def get_cs_links():
426 if action == 'push':
427 revs_limit = 5
428 revs = action_params.split(',')
429 cs_links = " " + ', '.join ([link(rev,
430 url('changeset_home',
431 repo_name=user_log.repository.repo_name,
432 revision=rev)) for rev in revs[:revs_limit] ])
433 if len(revs) > revs_limit:
434 uniq_id = revs[0]
435 html_tmpl = ('<span> %s '
436 '<a class="show_more" id="_%s" href="#">%s</a> '
437 '%s</span>')
438 cs_links += html_tmpl % (_('and'), uniq_id, _('%s more') \
439 % (len(revs) - revs_limit),
440 _('revisions'))
426 revs_limit = 5
427 revs = action_params.split(',')
428 cs_links = " " + ', '.join ([link(rev,
429 url('changeset_home',
430 repo_name=user_log.repository.repo_name,
431 revision=rev)) for rev in revs[:revs_limit] ])
432 if len(revs) > revs_limit:
433 uniq_id = revs[0]
434 html_tmpl = ('<span> %s '
435 '<a class="show_more" id="_%s" href="#">%s</a> '
436 '%s</span>')
437 cs_links += html_tmpl % (_('and'), uniq_id, _('%s more') \
438 % (len(revs) - revs_limit),
439 _('revisions'))
441 440
442 html_tmpl = '<span id="%s" style="display:none"> %s </span>'
443 cs_links += html_tmpl % (uniq_id, ', '.join([link(rev,
444 url('changeset_home',
445 repo_name=user_log.repository.repo_name,
446 revision=rev)) for rev in revs[revs_limit:] ]))
441 html_tmpl = '<span id="%s" style="display:none"> %s </span>'
442 cs_links += html_tmpl % (uniq_id, ', '.join([link(rev,
443 url('changeset_home',
444 repo_name=user_log.repository.repo_name,
445 revision=rev)) for rev in revs[revs_limit:] ]))
447 446
448 return cs_links
449 return ''
447 return cs_links
450 448
451 449 def get_fork_name():
452 450 repo_name = action_params
453 451 return str(link_to(action_params, url('summary_home',
454 452 repo_name=repo_name,)))
455 453
456 454 map = {'user_deleted_repo':(_('[deleted] repository'), None),
457 455 'user_created_repo':(_('[created] repository'), None),
458 'user_forked_repo':(_('[forked] repository'), get_fork_name),
456 'user_forked_repo':(_('[forked] repository as'), get_fork_name),
459 457 'user_updated_repo':(_('[updated] repository'), None),
460 458 'admin_deleted_repo':(_('[delete] repository'), None),
461 459 'admin_created_repo':(_('[created] repository'), None),
462 460 'admin_forked_repo':(_('[forked] repository'), None),
463 461 'admin_updated_repo':(_('[updated] repository'), None),
464 'push':(_('[pushed] into'), get_cs_links),
465 'pull':(_('[pulled] from'), None),
462 'push':(_('[pushed] '), get_cs_links),
463 'pull':(_('[pulled] '), None),
466 464 'started_following_repo':(_('[started following] repository'), None),
467 465 'stopped_following_repo':(_('[stopped following] repository'), None),
468 466 }
469 467
470 468 action_str = map.get(action, action)
471 469 action = action_str[0].replace('[', '<span class="journal_highlight">')\
472 470 .replace(']', '</span>')
473 471 action_params_func = lambda :""
474 472
475 473 if action_str[1] is not None:
476 474 action_params_func = action_str[1]
477 475
478 476 return literal(action + " " + action_params_func())
479 477
480 478 def action_parser_icon(user_log):
481 479 action = user_log.action
482 480 action_params = None
483 481 x = action.split(':')
484 482
485 483 if len(x) > 1:
486 484 action, action_params = x
487 485
488 486 tmpl = """<img src="%s/%s" alt="%s"/>"""
489 487 map = {'user_deleted_repo':'database_delete.png',
490 488 'user_created_repo':'database_add.png',
491 489 'user_forked_repo':'arrow_divide.png',
492 490 'user_updated_repo':'database_edit.png',
493 491 'admin_deleted_repo':'database_delete.png',
494 492 'admin_created_repo':'database_add.png',
495 493 'admin_forked_repo':'arrow_divide.png',
496 494 'admin_updated_repo':'database_edit.png',
497 495 'push':'script_add.png',
498 496 'pull':'down_16.png',
499 497 'started_following_repo':'heart_add.png',
500 498 'stopped_following_repo':'heart_delete.png',
501 499 }
502 500 return literal(tmpl % ((url('/images/icons/')),
503 501 map.get(action, action), action))
504 502
505 503
506 504 #==============================================================================
507 505 # PERMS
508 506 #==============================================================================
509 507 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
510 508 HasRepoPermissionAny, HasRepoPermissionAll
511 509
512 510 #==============================================================================
513 511 # GRAVATAR URL
514 512 #==============================================================================
515 513 import hashlib
516 514 import urllib
517 515 from pylons import request
518 516
519 517 def gravatar_url(email_address, size=30):
520 518 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
521 519 default = 'identicon'
522 520 baseurl_nossl = "http://www.gravatar.com/avatar/"
523 521 baseurl_ssl = "https://secure.gravatar.com/avatar/"
524 522 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
525 523
526 524
527 525 # construct the url
528 526 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
529 527 gravatar_url += urllib.urlencode({'d':default, 's':str(size)})
530 528
531 529 return gravatar_url
532 530
533 531 def safe_unicode(str):
534 532 """safe unicode function. In case of UnicodeDecode error we try to return
535 533 unicode with errors replace, if this failes we return unicode with
536 534 string_escape decoding """
537 535
538 536 try:
539 537 u_str = unicode(str)
540 538 except UnicodeDecodeError:
541 539 try:
542 540 u_str = unicode(str, 'utf-8', 'replace')
543 541 except UnicodeDecodeError:
544 542 #incase we have a decode error just represent as byte string
545 543 u_str = unicode(str(str).encode('string_escape'))
546 544
547 545 return u_str
548 546
549 547 def changed_tooltip(nodes):
550 548 if nodes:
551 549 pref = ': <br/> '
552 550 suf = ''
553 551 if len(nodes) > 30:
554 552 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
555 553 return literal(pref + '<br/> '.join([x.path for x in nodes[:30]]) + suf)
556 554 else:
557 555 return ': ' + _('No Files')
General Comments 0
You need to be logged in to leave comments. Login now