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