##// END OF EJS Templates
changes for release 1.1.5
marcink -
r1136:93b980eb default
parent child Browse files
Show More
@@ -1,131 +1,132 b''
1 1
2 2 =================================================
3 3 Welcome to RhodeCode (RhodiumCode) documentation!
4 4 =================================================
5 5
6 6 ``RhodeCode`` (formerly hg-app) is a Pylons framework based Mercurial repository
7 7 browser/management tool with a built in push/pull server and full text search.
8 8 It works on http/https and has a built in permission/authentication system with
9 9 the ability to authenticate via LDAP.
10 10
11 RhodeCode is similar in some respects to github or bitbucket,
12 however RhodeCode can be run as standalone hosted application on your own server. It is open source
13 and donation ware and focuses more on providing a customized, self administered
14 interface for Mercurial(and soon GIT) repositories. RhodeCode is powered by a vcs_
15 library that Lukasz Balcerzak and I created to handle multiple different version
16 control systems.
11 RhodeCode is similar in some respects to github or bitbucket_,
12 however RhodeCode can be run as standalone hosted application on your own server.
13 It is open source and donation ware and focuses more on providing a customized,
14 self administered interface for Mercurial(and soon GIT) repositories.
15 RhodeCode is powered by a vcs_ library that Lukasz Balcerzak and I created to
16 handle multiple different version control systems.
17 17
18 18 RhodeCode uses `Semantic Versioning <http://semver.org/>`_
19 19
20 20 RhodeCode demo
21 21 --------------
22 22
23 http://hg.python-works.com
23 http://demo.rhodecode.org
24 24
25 25 The default access is anonymous but you can login to an administrative account
26 26 using the following credentials:
27 27
28 28 - username: demo
29 29 - password: demo
30 30
31 31 Source code
32 32 -----------
33 33
34 The latest source for RhodeCode can be obtained from my own RhodeCode instance
35 https://rhodecode.org
34 The latest source for RhodeCode can be obtained from official RhodeCode instance
35 https://hg.rhodecode.org
36 36
37 37 Rarely updated source code and issue tracker is available at bitbcuket
38 38 http://bitbucket.org/marcinkuzminski/rhodecode
39 39
40 40 Installation
41 41 ------------
42 42
43 43 Please visit http://packages.python.org/RhodeCode/installation.html
44 44
45 45
46 46 RhodeCode Features
47 47 ------------------
48 48
49 49 - Has it's own middleware to handle mercurial_ protocol requests.
50 50 Each request can be logged and authenticated.
51 51 - Runs on threads unlike hgweb. You can make multiple pulls/pushes simultaneous. Supports http/https
52 52 and LDAP
53 53 - Full permissions (private/read/write/admin) and authentication per project.
54 54 One account for web interface and mercurial_ push/pull/clone operations.
55 55 - Mako templates let's you customize the look and feel of the application.
56 56 - Beautiful diffs, annotations and source code browsing all colored by pygments.
57 57 - Mercurial_ branch graph and yui-flot powered graphs with zooming and statistics
58 58 - Admin interface with user/permission management. Admin activity journal, logs
59 59 pulls, pushes, forks, registrations and other actions made by all users.
60 60 - Server side forks. It is possible to fork a project and modify it freely without
61 61 breaking the main repository.
62 62 - Full text search powered by Whoosh on the source files, and file names.
63 63 Build in indexing daemons, with optional incremental index build
64 64 (no external search servers required all in one application)
65 65 - Setup project descriptions and info inside built in db for easy, non
66 66 file-system operations
67 67 - Intelligent cache with invalidation after push or project change, provides high
68 68 performance and always up to date data.
69 69 - Rss / atom feeds, gravatar support, download sources as zip/tar/gz
70 70 - Async tasks for speed and performance using celery_ (works without them too)
71 71 - Backup scripts can do backup of whole app and send it over scp to desired
72 72 location
73 73 - Based on pylons / sqlalchemy / sqlite / whoosh / vcs
74 74
75 75
76 76 .. include:: ./docs/screenshots.rst
77 77
78 78
79 79 Incoming / Plans
80 80 ----------------
81 81
82 82 - Project grouping
83 83 - User groups/teams
84 84 - SSH based authentication with server side key management
85 85 - Code review (probably based on hg-review)
86 86 - Full git_ support, with push/pull server (currently in beta tests)
87 87 - Redmine integration
88 88 - Public accessible activity feeds
89 89 - Commit based built in wiki system
90 90 - Clone points and cloning from remote repositories into RhodeCode
91 91 - More statistics and graph (global annotation + some more statistics)
92 92 - Other advancements as development continues (or you can of course make additions and or requests)
93 93
94 94 License
95 95 -------
96 96
97 97 ``RhodeCode`` is released under the GPL_ license.
98 98
99 99
100 100 Mailing group Q&A
101 101 -----------------
102 102
103 103 Join the `Google group <http://groups.google.com/group/rhodecode>`_
104 104
105 105 Open an issue at `issue tracker <http://bitbucket.org/marcinkuzminski/rhodecode/issues>`_
106 106
107 107 Join #rhodecode on FreeNode (irc.freenode.net)
108 108 or use http://webchat.freenode.net/?channels=rhodecode for web access to irc.
109 109
110 110 Online documentation
111 111 --------------------
112 112
113 113 Online documentation for the current version of RhodeCode is available at
114 114 http://packages.python.org/RhodeCode/.
115 115 You may also build the documentation for yourself - go into ``docs/`` and run::
116 116
117 117 make html
118 118
119 119 (You need to have sphinx installed to build the documentation. If you don't
120 120 have sphinx installed you can install it via the command: ``easy_install sphinx``)
121 121
122 122 .. _virtualenv: http://pypi.python.org/pypi/virtualenv
123 123 .. _python: http://www.python.org/
124 124 .. _django: http://www.djangoproject.com/
125 125 .. _mercurial: http://mercurial.selenic.com/
126 .. _bitbucket: http://bitbucket.org/
126 127 .. _subversion: http://subversion.tigris.org/
127 128 .. _git: http://git-scm.com/
128 129 .. _celery: http://celeryproject.org/
129 130 .. _Sphinx: http://sphinx.pocoo.org/
130 131 .. _GPL: http://www.gnu.org/licenses/gpl.html
131 132 .. _vcs: http://pypi.python.org/pypi/vcs No newline at end of file
@@ -1,191 +1,213 b''
1 1 .. _changelog:
2 2
3 3 Changelog
4 4 =========
5 5
6
7 1.1.5 (**2011-03-1X**)
8 ======================
9
10 news
11 ----
12
13 - basic windows support, by exchanging pybcrypt into sha256 for windows only
14 highly inspired by idea of mantis406
15
16 fixes
17 -----
18
19 - fixed sorting by author in main page
20 - fixed crashes with diffs on binary files
21 - fixed #131 problem with boolean values for LDAP
22 - fixed #122 mysql problems thanks to striker69
23 - fixed problem with errors on calling raw/raw_files/annotate functions
24 with unknown revisions
25 - fixed returned rawfiles attachment names with international character
26 - cleaned out docs, big thanks to Jason Harris
27
6 28 1.1.4 (**2011-02-19**)
7 29 ======================
8 30
9 31 news
10 32 ----
11 33
12 34 fixes
13 35 -----
14 36
15 37 - fixed formencode import problem on settings page, that caused server crash
16 38 when that page was accessed as first after server start
17 39 - journal fixes
18 40 - fixed option to access repository just by entering http://server/<repo_name>
19 41
20 42
21 43 1.1.3 (**2011-02-16**)
22 44 ======================
23 45
24 46 news
25 47 ----
26 48
27 49 - implemented #102 allowing the '.' character in username
28 50 - added option to access repository just by entering http://server/<repo_name>
29 51 - celery task ignores result for better performance
30 52
31 53 fixes
32 54 -----
33 55
34 56 - fixed ehlo command and non auth mail servers on smtp_lib. Thanks to
35 57 apollo13 and Johan Walles
36 58 - small fixes in journal
37 59 - fixed problems with getting setting for celery from .ini files
38 60 - registration, password reset and login boxes share the same title as main
39 61 application now
40 62 - fixed #113: to high permissions to fork repository
41 63 - fixed problem with '[' chars in commit messages in journal
42 64 - removed issue with space inside renamed repository after deletion
43 65 - db transaction fixes when filesystem repository creation failed
44 66 - fixed #106 relation issues on databases different than sqlite
45 67 - fixed static files paths links to use of url() method
46 68
47 69 1.1.2 (**2011-01-12**)
48 70 ======================
49 71
50 72 news
51 73 ----
52 74
53 75
54 76 fixes
55 77 -----
56 78
57 79 - fixes #98 protection against float division of percentage stats
58 80 - fixed graph bug
59 81 - forced webhelpers version since it was making troubles during installation
60 82
61 83 1.1.1 (**2011-01-06**)
62 84 ======================
63 85
64 86 news
65 87 ----
66 88
67 89 - added force https option into ini files for easier https usage (no need to
68 90 set server headers with this options)
69 91 - small css updates
70 92
71 93 fixes
72 94 -----
73 95
74 96 - fixed #96 redirect loop on files view on repositories without changesets
75 97 - fixed #97 unicode string passed into server header in special cases (mod_wsgi)
76 98 and server crashed with errors
77 99 - fixed large tooltips problems on main page
78 100 - fixed #92 whoosh indexer is more error proof
79 101
80 102 1.1.0 (**2010-12-18**)
81 103 ======================
82 104
83 105 news
84 106 ----
85 107
86 108 - rewrite of internals for vcs >=0.1.10
87 109 - uses mercurial 1.7 with dotencode disabled for maintaining compatibility
88 110 with older clients
89 111 - anonymous access, authentication via ldap
90 112 - performance upgrade for cached repos list - each repository has it's own
91 113 cache that's invalidated when needed.
92 114 - performance upgrades on repositories with large amount of commits (20K+)
93 115 - main page quick filter for filtering repositories
94 116 - user dashboards with ability to follow chosen repositories actions
95 117 - sends email to admin on new user registration
96 118 - added cache/statistics reset options into repository settings
97 119 - more detailed action logger (based on hooks) with pushed changesets lists
98 120 and options to disable those hooks from admin panel
99 121 - introduced new enhanced changelog for merges that shows more accurate results
100 122 - new improved and faster code stats (based on pygments lexers mapping tables,
101 123 showing up to 10 trending sources for each repository. Additionally stats
102 124 can be disabled in repository settings.
103 125 - gui optimizations, fixed application width to 1024px
104 126 - added cut off (for large files/changesets) limit into config files
105 127 - whoosh, celeryd, upgrade moved to paster command
106 128 - other than sqlite database backends can be used
107 129
108 130 fixes
109 131 -----
110 132
111 133 - fixes #61 forked repo was showing only after cache expired
112 134 - fixes #76 no confirmation on user deletes
113 135 - fixes #66 Name field misspelled
114 136 - fixes #72 block user removal when he owns repositories
115 137 - fixes #69 added password confirmation fields
116 138 - fixes #87 RhodeCode crashes occasionally on updating repository owner
117 139 - fixes #82 broken annotations on files with more than 1 blank line at the end
118 140 - a lot of fixes and tweaks for file browser
119 141 - fixed detached session issues
120 142 - fixed when user had no repos he would see all repos listed in my account
121 143 - fixed ui() instance bug when global hgrc settings was loaded for server
122 144 instance and all hgrc options were merged with our db ui() object
123 145 - numerous small bugfixes
124 146
125 147 (special thanks for TkSoh for detailed feedback)
126 148
127 149
128 150 1.0.2 (**2010-11-12**)
129 151 ======================
130 152
131 153 news
132 154 ----
133 155
134 156 - tested under python2.7
135 157 - bumped sqlalchemy and celery versions
136 158
137 159 fixes
138 160 -----
139 161
140 162 - fixed #59 missing graph.js
141 163 - fixed repo_size crash when repository had broken symlinks
142 164 - fixed python2.5 crashes.
143 165
144 166
145 167 1.0.1 (**2010-11-10**)
146 168 ======================
147 169
148 170 news
149 171 ----
150 172
151 173 - small css updated
152 174
153 175 fixes
154 176 -----
155 177
156 178 - fixed #53 python2.5 incompatible enumerate calls
157 179 - fixed #52 disable mercurial extension for web
158 180 - fixed #51 deleting repositories don't delete it's dependent objects
159 181
160 182
161 183 1.0.0 (**2010-11-02**)
162 184 ======================
163 185
164 186 - security bugfix simplehg wasn't checking for permissions on commands
165 187 other than pull or push.
166 188 - fixed doubled messages after push or pull in admin journal
167 189 - templating and css corrections, fixed repo switcher on chrome, updated titles
168 190 - admin menu accessible from options menu on repository view
169 191 - permissions cached queries
170 192
171 193 1.0.0rc4 (**2010-10-12**)
172 194 ==========================
173 195
174 196 - fixed python2.5 missing simplejson imports (thanks to Jens BΓ€ckman)
175 197 - removed cache_manager settings from sqlalchemy meta
176 198 - added sqlalchemy cache settings to ini files
177 199 - validated password length and added second try of failure on paster setup-app
178 200 - fixed setup database destroy prompt even when there was no db
179 201
180 202
181 203 1.0.0rc3 (**2010-10-11**)
182 204 =========================
183 205
184 206 - fixed i18n during installation.
185 207
186 208 1.0.0rc2 (**2010-10-11**)
187 209 =========================
188 210
189 211 - Disabled dirsize in file browser, it's causing nasty bug when dir renames
190 212 occure. After vcs is fixed it'll be put back again.
191 213 - templating/css rewrites, optimized css. No newline at end of file
@@ -1,19 +1,25 b''
1 1 .. _contributing:
2 2
3 3 Contributing to RhodeCode
4 4 =========================
5 5
6 6 If you would like to contribute to RhodeCode, please contact me, any help is
7 7 greatly appreciated!
8 8
9 9 Could I request that you make your source contributions by first forking the
10 RhodeCode repository on bitbucket
10 RhodeCode repository on bitbucket_
11 11 https://bitbucket.org/marcinkuzminski/rhodecode and then make your changes to
12 your forked repository. Finally, when you are finished making a change, please
13 send me a pull request.
12 your forked repository. Please post all fixes into **BETA** branch since your
13 fix might be already fixed there and i try to merge all fixes from beta into
14 stable, and not the other way. Finally, when you are finished making a change,
15 please send me a pull request.
14 16
15 17 To run RhodeCode in a development version you always need to install the tip
16 18 version of RhodeCode and the VCS library.
17 19
18 20 | Thank you for any contributions!
19 | Marcin No newline at end of file
21 | Marcin
22
23
24
25 .. _bitbucket: http://bitbucket.org/
@@ -1,56 +1,57 b''
1 1 .. _index:
2 2
3 3 .. include:: ./../README.rst
4 4
5 5 Documentation
6 6 -------------
7 7
8 8 **Installation:**
9 9
10 10 .. toctree::
11 11 :maxdepth: 1
12 12
13 13 installation
14 14 setup
15 15 upgrade
16 16
17 17 **Usage**
18 18
19 19 .. toctree::
20 20 :maxdepth: 1
21 21
22 22 enable_git
23 23 statistics
24 24
25 25 **Develop**
26 26
27 27 .. toctree::
28 28 :maxdepth: 1
29 29
30 30 contributing
31 31 changelog
32 32
33 33 **API**
34 34
35 35 .. toctree::
36 36 :maxdepth: 2
37 37
38 38 api/index
39 39
40 40
41 41 Other topics
42 42 ------------
43 43
44 44 * :ref:`genindex`
45 45 * :ref:`search`
46 46
47 47 .. _virtualenv: http://pypi.python.org/pypi/virtualenv
48 48 .. _python: http://www.python.org/
49 49 .. _django: http://www.djangoproject.com/
50 50 .. _mercurial: http://mercurial.selenic.com/
51 .. _bitbucket: http://bitbucket.org/
51 52 .. _subversion: http://subversion.tigris.org/
52 53 .. _git: http://git-scm.com/
53 54 .. _celery: http://celeryproject.org/
54 55 .. _Sphinx: http://sphinx.pocoo.org/
55 56 .. _GPL: http://www.gnu.org/licenses/gpl.html
56 57 .. _vcs: http://pypi.python.org/pypi/vcs
@@ -1,105 +1,106 b''
1 1 .. _installation:
2 2
3 3 Installation
4 4 ============
5 5
6 6 ``RhodeCode`` is written entirely in Python. In order to gain maximum performance
7 7 there are some third-party you must install. When RhodeCode is used
8 8 together with celery you have to install some kind of message broker,
9 9 recommended one is rabbitmq_ to make the async tasks work.
10 10
11 11 Of course RhodeCode works in sync mode also and then you do not have to install
12 any third party applications. However, using Celery_ will give you a large speed improvement when using
13 many big repositories. If you plan to use RhodeCode for say 7 to 10 small repositories, RhodeCode
14 will perform perfectly well without celery running.
12 any third party applications. However, using Celery_ will give you a large
13 speed improvement when using many big repositories. If you plan to use
14 RhodeCode for say 7 to 10 small repositories, RhodeCode will perform perfectly
15 well without celery running.
15 16
16 If you make the decision to run RhodeCode with celery make sure you run celeryd using paster
17 and message broker together with the application.
17 If you make the decision to run RhodeCode with celery make sure you run
18 celeryd using paster and message broker together with the application.
18 19
19 20 Installing RhodeCode from Cheese Shop
20 21 -------------------------------------
21 22
22 23 Rhodecode requires python version 2.5 or higher.
23 24
24 25 The easiest way to install ``rhodecode`` is to run::
25 26
26 27 easy_install rhodecode
27 28
28 29 Or::
29 30
30 31 pip install rhodecode
31 32
32 33 If you prefer to install RhodeCode manually simply grab latest release from
33 34 http://pypi.python.org/pypi/rhodecode, decompress the archive and run::
34 35
35 36 python setup.py install
36 37
37 38
38 39 Step by step installation example
39 40 ---------------------------------
40 41
41 42
42 43 - Assuming you have installed virtualenv_ create a new virtual environment using virtualenv::
43 44
44 45 virtualenv --no-site-packages /var/www/rhodecode-venv
45 46
46 47
47 48 .. note:: Using ``--no-site-packages`` when generating your
48 49 virtualenv is **very important**. This flag provides the necessary
49 50 isolation for running the set of packages required by
50 51 RhodeCode. If you do not specify ``--no-site-packages``,
51 52 it's possible that RhodeCode will not install properly into
52 53 the virtualenv, or, even if it does, may not run properly,
53 54 depending on the packages you've already got installed into your
54 55 Python's "main" site-packages dir.
55 56
56 57
57 58 - this will install new virtualenv_ into `/var/www/rhodecode-venv`.
58 59 - Activate the virtualenv_ by running::
59 60
60 61 source /var/www/rhodecode-venv/bin/activate
61 62
62 63 .. note:: If you're using UNIX, *do not* use ``sudo`` to run the
63 64 ``virtualenv`` script. It's perfectly acceptable (and desirable)
64 65 to create a virtualenv as a normal user.
65 66
66 67 - Make a folder for rhodecode somewhere on the filesystem for example::
67 68
68 69 mkdir /var/www/rhodecode
69 70
70 71
71 72 - Run this command to install rhodecode::
72 73
73 74 easy_install rhodecode
74 75
75 76 - This will install rhodecode together with pylons and all other required python
76 77 libraries
77 78
78 79 Requirements for Celery (optional)
79 80 ----------------------------------
80 81
81 82 .. note::
82 83 Installing message broker and using celery is optional, RhodeCode will
83 84 work perfectly fine without them.
84 85
85 86
86 87 **Message Broker**
87 88
88 89 - preferred is `RabbitMq <http://www.rabbitmq.com/>`_
89 90 - A possible alternative is `Redis <http://code.google.com/p/redis/>`_
90 91
91 92 For installation instructions you can visit:
92 93 http://ask.github.com/celery/getting-started/index.html.
93 94 This is a very nice tutorial on how to start using celery_ with rabbitmq_
94 95
95 96
96 97 You can now proceed to :ref:`setup`
97 98 -----------------------------------
98 99
99 100
100 101
101 102 .. _virtualenv: http://pypi.python.org/pypi/virtualenv
102 103 .. _python: http://www.python.org/
103 104 .. _mercurial: http://mercurial.selenic.com/
104 105 .. _celery: http://celeryproject.org/
105 106 .. _rabbitmq: http://www.rabbitmq.com/ No newline at end of file
@@ -1,384 +1,384 b''
1 1 .. _setup:
2 2
3 3 Setup
4 4 =====
5 5
6 6
7 7 Setting up RhodeCode
8 8 --------------------------
9 9
10 10 First, you will need to create a RhodeCode configuration file. Run the following
11 11 command to do this::
12 12
13 13 paster make-config RhodeCode production.ini
14 14
15 15 - This will create the file `production.ini` in the current directory. This
16 16 configuration file contains the various settings for RhodeCode, e.g proxy port,
17 17 email settings, usage of static files, cache, celery settings and logging.
18 18
19 19
20 20 Next, you need to create the databases used by RhodeCode. I recommend that you
21 21 use sqlite (default) or postgresql. If you choose a database other than the
22 22 default ensure you properly adjust the db url in your production.ini
23 23 configuration file to use this other database. Create the databases by running
24 24 the following command::
25 25
26 26 paster setup-app production.ini
27 27
28 28 This will prompt you for a "root" path. This "root" path is the location where
29 29 RhodeCode will store all of its repositories on the current machine. After
30 30 entering this "root" path ``setup-app`` will also prompt you for a username and password
31 31 for the initial admin account which ``setup-app`` sets up for you.
32 32
33 33 - The ``setup-app`` command will create all of the needed tables and an admin
34 34 account. When choosing a root path you can either use a new empty location, or a
35 35 location which already contains existing repositories. If you choose a location
36 36 which contains existing repositories RhodeCode will simply add all of the
37 37 repositories at the chosen location to it's database. (Note: make sure you
38 38 specify the correct path to the root).
39 39 - Note: the given path for mercurial_ repositories **must** be write accessible
40 40 for the application. It's very important since the RhodeCode web interface will
41 41 work without write access, but when trying to do a push it will eventually fail
42 42 with permission denied errors unless it has write access.
43 43
44 44 You are now ready to use RhodeCode, to run it simply execute::
45 45
46 46 paster serve production.ini
47 47
48 48 - This command runs the RhodeCode server. The web app should be available at the
49 49 127.0.0.1:5000. This ip and port is configurable via the production.ini
50 50 file created in previous step
51 51 - Use the admin account you created above when running ``setup-app`` to login to the web app.
52 52 - The default permissions on each repository is read, and the owner is admin.
53 53 Remember to update these if needed.
54 54 - In the admin panel you can toggle ldap, anonymous, permissions settings. As
55 55 well as edit more advanced options on users and repositories
56 56
57 57 Try copying your own mercurial repository into the "root" directory you are
58 58 using, then from within the RhodeCode web application choose Admin >
59 59 repositories. Then choose Add New Repository. Add the repository you copied into
60 60 the root. Test that you can browse your repository from within RhodeCode and then
61 61 try cloning your repository from RhodeCode with::
62 62
63 63 hg clone http://127.0.0.1:5000/<repository name>
64 64
65 65 where *repository name* is replaced by the name of your repository.
66 66
67 67 Using RhodeCode with SSH
68 68 ------------------------
69 69
70 70 RhodeCode currently only hosts repositories using http and https. (The addition of
71 71 ssh hosting is a planned future feature.) However you can easily use ssh in
72 72 parallel with RhodeCode. (Repository access via ssh is a standard "out of
73 73 the box" feature of mercurial_ and you can use this to access any of the
74 74 repositories that RhodeCode is hosting. See PublishingRepositories_)
75 75
76 76 RhodeCode repository structures are kept in directories with the same name
77 77 as the project. When using repository groups, each group is a subdirectory.
78 78 This allows you to easily use ssh for accessing repositories.
79 79
80 80 In order to use ssh you need to make sure that your web-server and the users login
81 81 accounts have the correct permissions set on the appropriate directories. (Note
82 82 that these permissions are independent of any permissions you have set up using
83 83 the RhodeCode web interface.)
84 84
85 85 If your main directory (the same as set in RhodeCode settings) is for example
86 86 set to **/home/hg** and the repository you are using is named `rhodecode`, then
87 87 to clone via ssh you should run::
88 88
89 89 hg clone ssh://user@server.com/home/hg/rhodecode
90 90
91 91 Using other external tools such as mercurial-server_ or using ssh key based
92 92 authentication is fully supported.
93 93
94 94 Note: In an advanced setup, in order for your ssh access to use the same
95 95 permissions as set up via the RhodeCode web interface, you can create an
96 96 authentication hook to connect to the rhodecode db and runs check functions for
97 97 permissions against that.
98 98
99 99
100 100
101 101 Setting up Whoosh full text search
102 102 ----------------------------------
103 103
104 104 Starting from version 1.1 the whoosh index can be build by using the paster
105 105 command ``make-index``. To use ``make-index`` you must specify the configuration
106 106 file that stores the location of the index, and the location of the repositories
107 107 (`--repo-location`).
108 108
109 109 You may optionally pass the option `-f` to enable a full index rebuild. Without
110 110 the `-f` option, indexing will run always in "incremental" mode.
111 111
112 112 For an incremental index build use::
113 113
114 114 paster make-index production.ini --repo-location=<location for repos>
115 115
116 116 For a full index rebuild use::
117 117
118 118 paster make-index production.ini -f --repo-location=<location for repos>
119 119
120 120 - For full text search you can either put crontab entry for
121 121
122 122 In order to do periodical index builds and keep your index always up to date.
123 123 It's recommended to do a crontab entry for incremental indexing.
124 124 An example entry might look like this::
125 125
126 126 /path/to/python/bin/paster /path/to/rhodecode/production.ini --repo-location=<location for repos>
127 127
128 128 When using incremental mode (the default) whoosh will check the last
129 129 modification date of each file and add it to be reindexed if a newer file is
130 130 available. The indexing daemon checks for any removed files and removes them
131 131 from index.
132 132
133 133 If you want to rebuild index from scratch, you can use the `-f` flag as above,
134 134 or in the admin panel you can check `build from scratch` flag.
135 135
136 136
137 137 Setting up LDAP support
138 138 -----------------------
139 139
140 140 RhodeCode starting from version 1.1 supports ldap authentication. In order
141 141 to use LDAP, you have to install the python-ldap_ package. This package is available
142 142 via pypi, so you can install it by running
143 143
144 144 ::
145 145
146 146 easy_install python-ldap
147 147
148 148 ::
149 149
150 150 pip install python-ldap
151 151
152 152 .. note::
153 153 python-ldap requires some certain libs on your system, so before installing
154 154 it check that you have at least `openldap`, and `sasl` libraries.
155 155
156 156 ldap settings are located in admin->ldap section,
157 157
158 158 Here's a typical ldap setup::
159 159
160 160 Enable ldap = checked #controls if ldap access is enabled
161 161 Host = host.domain.org #actual ldap server to connect
162 162 Port = 389 or 689 for ldaps #ldap server ports
163 163 Enable LDAPS = unchecked #enable disable ldaps
164 164 Account = <account> #access for ldap server(if required)
165 165 Password = <password> #password for ldap server(if required)
166 166 Base DN = uid=%(user)s,CN=users,DC=host,DC=domain,DC=org
167 167
168 168
169 169 `Account` and `Password` are optional, and used for two-phase ldap
170 170 authentication so those are credentials to access your ldap, if it doesn't
171 171 support anonymous search/user lookups.
172 172
173 173 Base DN must have the %(user)s template inside, it's a place holder where your uid
174 174 used to login would go. It allows admins to specify non-standard schema for the
175 175 uid variable.
176 176
177 177 If all of the data is correctly entered, and `python-ldap` is properly
178 178 installed, then users should be granted access to RhodeCode with ldap accounts.
179 179 When logging in the first time a special ldap account is created inside
180 180 RhodeCode, so you can control the permissions even on ldap users. If such users
181 181 already exist in the RhodeCode database, then the ldap user with the same
182 182 username would be not be able to access RhodeCode.
183 183
184 184 If you have problems with ldap access and believe you have correctly entered the
185 185 required information then proceed by investigating the RhodeCode logs. Any
186 186 error messages sent from ldap will be saved there.
187 187
188 188
189 189
190 190 Setting Up Celery
191 191 -----------------
192 192
193 193 Since version 1.1 celery is configured by the rhodecode ini configuration files.
194 194 Simply set use_celery=true in the ini file then add / change the configuration
195 195 variables inside the ini file.
196 196
197 197 Remember that the ini files use the format with '.' not with '_' like celery.
198 198 So for example setting `BROKER_HOST` in celery means setting `broker.host` in
199 199 the config file.
200 200
201 201 In order to start using celery run::
202 202
203 203 paster celeryd <configfile.ini>
204 204
205 205
206 206 .. note::
207 207 Make sure you run this command from the same virtualenv, and with the same user
208 208 that rhodecode runs.
209 209
210 210 HTTPS support
211 211 -------------
212 212
213 213 There are two ways to enable https:
214 214
215 215 - Set HTTP_X_URL_SCHEME in your http server headers, than rhodecode will
216 216 recognize this headers and make proper https redirections
217 217 - Alternatively, set `force_https = true` in the ini configuration to force using
218 218 https, no headers are needed than to enable https
219 219
220 220
221 221 Nginx virtual host example
222 222 --------------------------
223 223
224 224 Sample config for nginx using proxy::
225 225
226 226 server {
227 227 listen 80;
228 228 server_name hg.myserver.com;
229 229 access_log /var/log/nginx/rhodecode.access.log;
230 230 error_log /var/log/nginx/rhodecode.error.log;
231 231 location / {
232 232 root /var/www/rhodecode/rhodecode/public/;
233 233 if (!-f $request_filename){
234 234 proxy_pass http://127.0.0.1:5000;
235 235 }
236 236 #this is important if you want to use https !!!
237 237 proxy_set_header X-Url-Scheme $scheme;
238 238 include /etc/nginx/proxy.conf;
239 239 }
240 240 }
241 241
242 242 Here's the proxy.conf. It's tuned so it will not timeout on long
243 243 pushes or large pushes::
244 244
245 245 proxy_redirect off;
246 246 proxy_set_header Host $host;
247 247 proxy_set_header X-Host $http_host;
248 248 proxy_set_header X-Real-IP $remote_addr;
249 249 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
250 250 proxy_set_header Proxy-host $proxy_host;
251 251 client_max_body_size 400m;
252 252 client_body_buffer_size 128k;
253 253 proxy_buffering off;
254 254 proxy_connect_timeout 3600;
255 255 proxy_send_timeout 3600;
256 256 proxy_read_timeout 3600;
257 257 proxy_buffer_size 16k;
258 258 proxy_buffers 4 16k;
259 259 proxy_busy_buffers_size 64k;
260 260 proxy_temp_file_write_size 64k;
261 261
262 262 Also, when using root path with nginx you might set the static files to false
263 263 in the production.ini file::
264 264
265 265 [app:main]
266 266 use = egg:rhodecode
267 267 full_stack = true
268 268 static_files = false
269 269 lang=en
270 270 cache_dir = %(here)s/data
271 271
272 272 In order to not have the statics served by the application. This improves speed.
273 273
274 274
275 275 Apache virtual host example
276 276 ---------------------------
277 277
278 278 Here is a sample configuration file for apache using proxy::
279 279
280 280 <VirtualHost *:80>
281 281 ServerName hg.myserver.com
282 282 ServerAlias hg.myserver.com
283 283
284 284 <Proxy *>
285 285 Order allow,deny
286 286 Allow from all
287 287 </Proxy>
288 288
289 289 #important !
290 290 #Directive to properly generate url (clone url) for pylons
291 291 ProxyPreserveHost On
292 292
293 293 #rhodecode instance
294 294 ProxyPass / http://127.0.0.1:5000/
295 295 ProxyPassReverse / http://127.0.0.1:5000/
296 296
297 297 #to enable https use line below
298 298 #SetEnvIf X-Url-Scheme https HTTPS=1
299 299
300 300 </VirtualHost>
301 301
302 302
303 303 Additional tutorial
304 304 http://wiki.pylonshq.com/display/pylonscookbook/Apache+as+a+reverse+proxy+for+Pylons
305 305
306 306
307 307 Apache as subdirectory
308 308 ----------------------
309 309
310 310
311 311 Apache subdirectory part::
312 312
313 313 <Location /rhodecode>
314 314 ProxyPass http://127.0.0.1:59542/rhodecode
315 315 ProxyPassReverse http://127.0.0.1:59542/rhodecode
316 316 SetEnvIf X-Url-Scheme https HTTPS=1
317 317 </Location>
318 318
319 319 Besides the regular apache setup you will need to add the following to your .ini file::
320 320
321 321 filter-with = proxy-prefix
322 322
323 323 Add the following at the end of the .ini file::
324 324
325 325 [filter:proxy-prefix]
326 326 use = egg:PasteDeploy#prefix
327 327 prefix = /<someprefix>
328 328
329 329
330 330 Apache's example FCGI config
331 331 ----------------------------
332 332
333 333 TODO !
334 334
335 335 Other configuration files
336 336 -------------------------
337 337
338 338 Some example init.d scripts can be found here, for debian and gentoo:
339 339
340 340 https://rhodeocode.org/rhodecode/files/tip/init.d
341 341
342 342
343 343 Troubleshooting
344 344 ---------------
345 345
346 346 :Q: **Missing static files?**
347 347 :A: Make sure either to set the `static_files = true` in the .ini file or
348 348 double check the root path for your http setup. It should point to
349 349 for example:
350 350 /home/my-virtual-python/lib/python2.6/site-packages/rhodecode/public
351
352 |
351
352 |
353 353
354 354 :Q: **Can't install celery/rabbitmq**
355 355 :A: Don't worry RhodeCode works without them too. No extra setup is required.
356 356
357 357 |
358
358
359 359 :Q: **Long lasting push timeouts?**
360 360 :A: Make sure you set a longer timeouts in your proxy/fcgi settings, timeouts
361 361 are caused by https server and not RhodeCode.
362
363 |
362
363 |
364 364
365 365 :Q: **Large pushes timeouts?**
366 366 :A: Make sure you set a proper max_body_size for the http server.
367 367
368 368 |
369 369
370 370 :Q: **Apache doesn't pass basicAuth on pull/push?**
371 371 :A: Make sure you added `WSGIPassAuthorization true`.
372 372
373 373 For further questions search the `Issues tracker`_, or post a message in the `google group rhodecode`_
374 374
375 375 .. _virtualenv: http://pypi.python.org/pypi/virtualenv
376 376 .. _python: http://www.python.org/
377 377 .. _mercurial: http://mercurial.selenic.com/
378 378 .. _celery: http://celeryproject.org/
379 379 .. _rabbitmq: http://www.rabbitmq.com/
380 380 .. _python-ldap: http://www.python-ldap.org/
381 381 .. _mercurial-server: http://www.lshift.net/mercurial-server.html
382 382 .. _PublishingRepositories: http://mercurial.selenic.com/wiki/PublishingRepositories
383 383 .. _Issues tracker: https://bitbucket.org/marcinkuzminski/rhodecode/issues
384 384 .. _google group rhodecode: http://groups.google.com/group/rhodecode No newline at end of file
@@ -1,51 +1,53 b''
1 1 .. _upgrade:
2 2
3 3 Upgrade
4 4 =======
5 5
6 6 Upgrading from Cheese Shop
7 7 --------------------------
8 8
9 9 .. note::
10 Firstly, it is recommended that you **always** perform a database backup before doing an upgrade.
10 Firstly, it is recommended that you **always** perform a database backup
11 before doing an upgrade.
11 12
12 13 The easiest way to upgrade ``rhodecode`` is to run::
13 14
14 15 easy_install -U rhodecode
15 16
16 17 Or::
17 18
18 19 pip install --upgrade rhodecode
19 20
20 21
21 22 Then make sure you run the following command from the installation directory::
22 23
23 24 paster make-config RhodeCode production.ini
24 25
25 26 This will display any changes made by the new version of RhodeCode to your
26 27 current configuration. It will try to perform an automerge. It's always better
27 to make a backup of your configuration file before hand and recheck the content after the automerge.
28 to make a backup of your configuration file before hand and recheck the
29 content after the automerge.
28 30
29 31 .. note::
30 32 The next steps only apply to upgrading from non bugfix releases eg. from
31 33 any minor or major releases. Bugfix releases (eg. 1.1.2->1.1.3) will
32 34 not have any database schema changes or whoosh library updates.
33 35
34 It is also recommended that you rebuild the whoosh index after upgrading since the new whoosh
35 version could introduce some incompatible index changes.
36 It is also recommended that you rebuild the whoosh index after upgrading since
37 the new whoosh version could introduce some incompatible index changes.
36 38
37 39
38 40 The final step is to upgrade the database. To do this simply run::
39 41
40 42 paster upgrade-db production.ini
41 43
42 44 This will upgrade the schema and update some of the defaults in the database,
43 and will always recheck the settings of the application, if there are no new options
44 that need to be set.
45 and will always recheck the settings of the application, if there are no new
46 options that need to be set.
45 47
46 48
47 49 .. _virtualenv: http://pypi.python.org/pypi/virtualenv
48 50 .. _python: http://www.python.org/
49 51 .. _mercurial: http://mercurial.selenic.com/
50 52 .. _celery: http://celeryproject.org/
51 53 .. _rabbitmq: http://www.rabbitmq.com/ No newline at end of file
@@ -1,52 +1,53 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.__init__
4 4 ~~~~~~~~~~~~~~~~~~
5 5
6 6 RhodeCode, a web based repository management based on pylons
7 7 versioning implementation: http://semver.org/
8 8
9 9 :created_on: Apr 9, 2010
10 10 :author: marcink
11 11 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
12 12 :license: GPLv3, see COPYING for more details.
13 13 """
14 14 # This program is free software; you can redistribute it and/or
15 15 # modify it under the terms of the GNU General Public License
16 16 # as published by the Free Software Foundation; version 2
17 17 # of the License or (at your opinion) any later version of the license.
18 18 #
19 19 # This program is distributed in the hope that it will be useful,
20 20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 22 # GNU General Public License for more details.
23 23 #
24 24 # You should have received a copy of the GNU General Public License
25 25 # along with this program; if not, write to the Free Software
26 26 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
27 27 # MA 02110-1301, USA.
28
28 import platform
29 29
30 VERSION = (1, 1, 4)
30 VERSION = (1, 1, 5)
31 31 __version__ = '.'.join((str(each) for each in VERSION[:4]))
32 32 __dbversion__ = 2 #defines current db version for migrations
33 __platform__ = platform.system()
33 34
34 35 try:
35 36 from rhodecode.lib.utils import get_current_revision
36 37 _rev = get_current_revision()
37 38 except ImportError:
38 39 #this is needed when doing some setup.py operations
39 40 _rev = False
40 41
41 42 if len(VERSION) > 3 and _rev:
42 43 __version__ += ' [rev:%s]' % _rev[0]
43 44
44 45 def get_version():
45 46 """Returns shorter version (digit parts only) as string."""
46 47
47 48 return '.'.join((str(each) for each in VERSION[:3]))
48 49
49 50 BACKENDS = {
50 51 'hg': 'Mercurial repository',
51 52 #'git': 'Git repository',
52 53 }
@@ -1,218 +1,218 b''
1 1 ################################################################################
2 2 ################################################################################
3 3 # RhodeCode - Pylons environment configuration #
4 4 # #
5 5 # The %(here)s variable will be replaced with the parent directory of this file#
6 6 ################################################################################
7 7
8 8 [DEFAULT]
9 9 debug = true
10 10 ################################################################################
11 11 ## Uncomment and replace with the address which should receive ##
12 12 ## any error reports after application crash ##
13 13 ## Additionally those settings will be used by RhodeCode mailing system ##
14 14 ################################################################################
15 15 #email_to = admin@localhost
16 16 #error_email_from = paste_error@localhost
17 17 #app_email_from = rhodecode-noreply@localhost
18 18 #error_message =
19 19
20 20 #smtp_server = mail.server.com
21 21 #smtp_username =
22 22 #smtp_password =
23 23 #smtp_port =
24 24 #smtp_use_tls = false
25 25 #smtp_use_ssl = true
26 26
27 27 [server:main]
28 28 ##nr of threads to spawn
29 29 threadpool_workers = 5
30 30
31 31 ##max request before thread respawn
32 32 threadpool_max_requests = 10
33 33
34 34 ##option to use threads of process
35 35 use_threadpool = true
36 36
37 37 use = egg:Paste#http
38 38 host = 127.0.0.1
39 39 port = 5000
40 40
41 41 [app:main]
42 42 use = egg:rhodecode
43 43 full_stack = true
44 44 static_files = true
45 45 lang=en
46 46 cache_dir = %(here)s/data
47 47 index_dir = %(here)s/data/index
48 48 app_instance_uuid = ${app_instance_uuid}
49 49 cut_off_limit = 256000
50 50 force_https = false
51 51
52 52 ####################################
53 53 ### CELERY CONFIG ####
54 54 ####################################
55 55 use_celery = false
56 56 broker.host = localhost
57 57 broker.vhost = rabbitmqhost
58 58 broker.port = 5672
59 59 broker.user = rabbitmq
60 60 broker.password = qweqwe
61 61
62 62 celery.imports = rhodecode.lib.celerylib.tasks
63 63
64 64 celery.result.backend = amqp
65 65 celery.result.dburi = amqp://
66 66 celery.result.serialier = json
67 67
68 68 #celery.send.task.error.emails = true
69 69 #celery.amqp.task.result.expires = 18000
70 70
71 71 celeryd.concurrency = 2
72 72 #celeryd.log.file = celeryd.log
73 73 celeryd.log.level = debug
74 celeryd.max.tasks.per.child = 3
74 celeryd.max.tasks.per.child = 1
75 75
76 76 #tasks will never be sent to the queue, but executed locally instead.
77 77 celery.always.eager = false
78 78
79 79 ####################################
80 80 ### BEAKER CACHE ####
81 81 ####################################
82 82 beaker.cache.data_dir=%(here)s/data/cache/data
83 83 beaker.cache.lock_dir=%(here)s/data/cache/lock
84 84
85 85 beaker.cache.regions=super_short_term,short_term,long_term,sql_cache_short,sql_cache_med,sql_cache_long
86 86
87 87 beaker.cache.super_short_term.type=memory
88 88 beaker.cache.super_short_term.expire=10
89 89
90 90 beaker.cache.short_term.type=memory
91 91 beaker.cache.short_term.expire=60
92 92
93 93 beaker.cache.long_term.type=memory
94 94 beaker.cache.long_term.expire=36000
95 95
96 96 beaker.cache.sql_cache_short.type=memory
97 97 beaker.cache.sql_cache_short.expire=10
98 98
99 99 beaker.cache.sql_cache_med.type=memory
100 100 beaker.cache.sql_cache_med.expire=360
101 101
102 102 beaker.cache.sql_cache_long.type=file
103 103 beaker.cache.sql_cache_long.expire=3600
104 104
105 105 ####################################
106 106 ### BEAKER SESSION ####
107 107 ####################################
108 108 ## Type of storage used for the session, current types are
109 109 ## dbm, file, memcached, database, and memory.
110 110 ## The storage uses the Container API
111 111 ##that is also used by the cache system.
112 112 beaker.session.type = file
113 113
114 114 beaker.session.key = rhodecode
115 115 beaker.session.secret = ${app_instance_secret}
116 116 beaker.session.timeout = 36000
117 117
118 118 ##auto save the session to not to use .save()
119 119 beaker.session.auto = False
120 120
121 121 ##true exire at browser close
122 122 #beaker.session.cookie_expires = 3600
123 123
124 124
125 125 ################################################################################
126 126 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
127 127 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
128 128 ## execute malicious code after an exception is raised. ##
129 129 ################################################################################
130 130 set debug = false
131 131
132 132 ##################################
133 133 ### LOGVIEW CONFIG ###
134 134 ##################################
135 135 logview.sqlalchemy = #faa
136 136 logview.pylons.templating = #bfb
137 137 logview.pylons.util = #eee
138 138
139 139 #########################################################
140 140 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
141 141 #########################################################
142 142 sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db
143 143 #sqlalchemy.db1.echo = False
144 144 #sqlalchemy.db1.pool_recycle = 3600
145 145 sqlalchemy.convert_unicode = true
146 146
147 147 ################################
148 148 ### LOGGING CONFIGURATION ####
149 149 ################################
150 150 [loggers]
151 151 keys = root, routes, rhodecode, sqlalchemy,beaker,templates
152 152
153 153 [handlers]
154 154 keys = console
155 155
156 156 [formatters]
157 157 keys = generic,color_formatter
158 158
159 159 #############
160 160 ## LOGGERS ##
161 161 #############
162 162 [logger_root]
163 163 level = INFO
164 164 handlers = console
165 165
166 166 [logger_routes]
167 167 level = INFO
168 168 handlers = console
169 169 qualname = routes.middleware
170 170 # "level = DEBUG" logs the route matched and routing variables.
171 171 propagate = 0
172 172
173 173 [logger_beaker]
174 174 level = ERROR
175 175 handlers = console
176 176 qualname = beaker.container
177 177 propagate = 0
178 178
179 179 [logger_templates]
180 180 level = INFO
181 181 handlers = console
182 182 qualname = pylons.templating
183 183 propagate = 0
184 184
185 185 [logger_rhodecode]
186 186 level = DEBUG
187 187 handlers = console
188 188 qualname = rhodecode
189 189 propagate = 0
190 190
191 191 [logger_sqlalchemy]
192 192 level = ERROR
193 193 handlers = console
194 194 qualname = sqlalchemy.engine
195 195 propagate = 0
196 196
197 197 ##############
198 198 ## HANDLERS ##
199 199 ##############
200 200
201 201 [handler_console]
202 202 class = StreamHandler
203 203 args = (sys.stderr,)
204 204 level = NOTSET
205 205 formatter = color_formatter
206 206
207 207 ################
208 208 ## FORMATTERS ##
209 209 ################
210 210
211 211 [formatter_generic]
212 212 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
213 213 datefmt = %Y-%m-%d %H:%M:%S
214 214
215 215 [formatter_color_formatter]
216 216 class=rhodecode.lib.colored_formatter.ColorFormatter
217 217 format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
218 218 datefmt = %Y-%m-%d %H:%M:%S No newline at end of file
@@ -1,340 +1,340 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.admin.settings
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 settings controller for rhodecode admin
7 7
8 8 :created_on: Jul 14, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software; you can redistribute it and/or
14 14 # modify it under the terms of the GNU General Public License
15 15 # as published by the Free Software Foundation; version 2
16 16 # of the License or (at your opinion) any later version of the license.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program; if not, write to the Free Software
25 25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 26 # MA 02110-1301, USA.
27 27
28 28 from formencode import htmlfill
29 29 from pylons import request, session, tmpl_context as c, url, app_globals as g, \
30 30 config
31 31 from pylons.controllers.util import abort, redirect
32 32 from pylons.i18n.translation import _
33 33 from rhodecode.lib import helpers as h
34 34 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
35 35 HasPermissionAnyDecorator, NotAnonymous
36 36 from rhodecode.lib.base import BaseController, render
37 37 from rhodecode.lib.celerylib import tasks, run_task
38 38 from rhodecode.lib.utils import repo2db_mapper, invalidate_cache, \
39 39 set_rhodecode_config
40 40 from rhodecode.model.db import RhodeCodeUi, Repository
41 41 from rhodecode.model.forms import UserForm, ApplicationSettingsForm, \
42 42 ApplicationUiSettingsForm
43 43 from rhodecode.model.scm import ScmModel
44 44 from rhodecode.model.settings import SettingsModel
45 45 from rhodecode.model.user import UserModel
46 46 from sqlalchemy import func
47 47 import formencode
48 48 import logging
49 49 import traceback
50 50
51 51 log = logging.getLogger(__name__)
52 52
53 53
54 54 class SettingsController(BaseController):
55 55 """REST Controller styled on the Atom Publishing Protocol"""
56 56 # To properly map this controller, ensure your config/routing.py
57 57 # file has a resource setup:
58 58 # map.resource('setting', 'settings', controller='admin/settings',
59 59 # path_prefix='/admin', name_prefix='admin_')
60 60
61 61
62 62 @LoginRequired()
63 63 def __before__(self):
64 64 c.admin_user = session.get('admin_user')
65 65 c.admin_username = session.get('admin_username')
66 66 super(SettingsController, self).__before__()
67 67
68 68
69 69 @HasPermissionAllDecorator('hg.admin')
70 70 def index(self, format='html'):
71 71 """GET /admin/settings: All items in the collection"""
72 72 # url('admin_settings')
73 73
74 74 defaults = SettingsModel().get_app_settings()
75 75 defaults.update(self.get_hg_ui_settings())
76 76 return htmlfill.render(
77 77 render('admin/settings/settings.html'),
78 78 defaults=defaults,
79 79 encoding="UTF-8",
80 80 force_defaults=False
81 81 )
82 82
83 83 @HasPermissionAllDecorator('hg.admin')
84 84 def create(self):
85 85 """POST /admin/settings: Create a new item"""
86 86 # url('admin_settings')
87 87
88 88 @HasPermissionAllDecorator('hg.admin')
89 89 def new(self, format='html'):
90 90 """GET /admin/settings/new: Form to create a new item"""
91 91 # url('admin_new_setting')
92 92
93 93 @HasPermissionAllDecorator('hg.admin')
94 94 def update(self, setting_id):
95 95 """PUT /admin/settings/setting_id: Update an existing item"""
96 96 # Forms posted to this method should contain a hidden field:
97 97 # <input type="hidden" name="_method" value="PUT" />
98 98 # Or using helpers:
99 99 # h.form(url('admin_setting', setting_id=ID),
100 100 # method='put')
101 101 # url('admin_setting', setting_id=ID)
102 102 if setting_id == 'mapping':
103 103 rm_obsolete = request.POST.get('destroy', False)
104 104 log.debug('Rescanning directories with destroy=%s', rm_obsolete)
105 105
106 106 initial = ScmModel().repo_scan(g.paths[0][1], g.baseui)
107 107 for repo_name in initial.keys():
108 108 invalidate_cache('get_repo_cached_%s' % repo_name)
109 109
110 110 repo2db_mapper(initial, rm_obsolete)
111 111
112 112 h.flash(_('Repositories successfully rescanned'), category='success')
113 113
114 114 if setting_id == 'whoosh':
115 115 repo_location = self.get_hg_ui_settings()['paths_root_path']
116 116 full_index = request.POST.get('full_index', False)
117 117 task = run_task(tasks.whoosh_index, repo_location, full_index)
118 118
119 119 h.flash(_('Whoosh reindex task scheduled'), category='success')
120 120 if setting_id == 'global':
121 121
122 122 application_form = ApplicationSettingsForm()()
123 123 try:
124 124 form_result = application_form.to_python(dict(request.POST))
125 125 settings_model = SettingsModel()
126 126 try:
127 127 hgsettings1 = settings_model.get('title')
128 128 hgsettings1.app_settings_value = form_result['rhodecode_title']
129 129
130 130 hgsettings2 = settings_model.get('realm')
131 131 hgsettings2.app_settings_value = form_result['rhodecode_realm']
132 132
133 133
134 134 self.sa.add(hgsettings1)
135 135 self.sa.add(hgsettings2)
136 136 self.sa.commit()
137 137 set_rhodecode_config(config)
138 138 h.flash(_('Updated application settings'),
139 139 category='success')
140 140
141 141 except:
142 142 log.error(traceback.format_exc())
143 h.flash(_('error occurred during updating application settings'),
144 category='error')
143 h.flash(_('error occurred during updating'
144 ' application settings'), category='error')
145 145
146 146 self.sa.rollback()
147 147
148 148
149 149 except formencode.Invalid, errors:
150 150 return htmlfill.render(
151 151 render('admin/settings/settings.html'),
152 152 defaults=errors.value,
153 153 errors=errors.error_dict or {},
154 154 prefix_error=False,
155 155 encoding="UTF-8")
156 156
157 157 if setting_id == 'mercurial':
158 158 application_form = ApplicationUiSettingsForm()()
159 159 try:
160 160 form_result = application_form.to_python(dict(request.POST))
161 161
162 162 try:
163 163
164 164 hgsettings1 = self.sa.query(RhodeCodeUi)\
165 165 .filter(RhodeCodeUi.ui_key == 'push_ssl').one()
166 166 hgsettings1.ui_value = form_result['web_push_ssl']
167 167
168 168 hgsettings2 = self.sa.query(RhodeCodeUi)\
169 169 .filter(RhodeCodeUi.ui_key == '/').one()
170 170 hgsettings2.ui_value = form_result['paths_root_path']
171 171
172 172
173 173 #HOOKS
174 174 hgsettings3 = self.sa.query(RhodeCodeUi)\
175 175 .filter(RhodeCodeUi.ui_key == 'changegroup.update').one()
176 176 hgsettings3.ui_active = bool(form_result['hooks_changegroup_update'])
177 177
178 178 hgsettings4 = self.sa.query(RhodeCodeUi)\
179 179 .filter(RhodeCodeUi.ui_key == 'changegroup.repo_size').one()
180 180 hgsettings4.ui_active = bool(form_result['hooks_changegroup_repo_size'])
181 181
182 182 hgsettings5 = self.sa.query(RhodeCodeUi)\
183 183 .filter(RhodeCodeUi.ui_key == 'pretxnchangegroup.push_logger').one()
184 184 hgsettings5.ui_active = bool(form_result['hooks_pretxnchangegroup_push_logger'])
185 185
186 186 hgsettings6 = self.sa.query(RhodeCodeUi)\
187 187 .filter(RhodeCodeUi.ui_key == 'preoutgoing.pull_logger').one()
188 188 hgsettings6.ui_active = bool(form_result['hooks_preoutgoing_pull_logger'])
189 189
190 190
191 191 self.sa.add(hgsettings1)
192 192 self.sa.add(hgsettings2)
193 193 self.sa.add(hgsettings3)
194 194 self.sa.add(hgsettings4)
195 195 self.sa.add(hgsettings5)
196 196 self.sa.add(hgsettings6)
197 197 self.sa.commit()
198 198
199 199 h.flash(_('Updated mercurial settings'),
200 200 category='success')
201 201
202 202 except:
203 203 log.error(traceback.format_exc())
204 204 h.flash(_('error occurred during updating application settings'),
205 205 category='error')
206 206
207 207 self.sa.rollback()
208 208
209 209
210 210 except formencode.Invalid, errors:
211 211 return htmlfill.render(
212 212 render('admin/settings/settings.html'),
213 213 defaults=errors.value,
214 214 errors=errors.error_dict or {},
215 215 prefix_error=False,
216 216 encoding="UTF-8")
217 217
218 218
219 219
220 220 return redirect(url('admin_settings'))
221 221
222 222 @HasPermissionAllDecorator('hg.admin')
223 223 def delete(self, setting_id):
224 224 """DELETE /admin/settings/setting_id: Delete an existing item"""
225 225 # Forms posted to this method should contain a hidden field:
226 226 # <input type="hidden" name="_method" value="DELETE" />
227 227 # Or using helpers:
228 228 # h.form(url('admin_setting', setting_id=ID),
229 229 # method='delete')
230 230 # url('admin_setting', setting_id=ID)
231 231
232 232 @HasPermissionAllDecorator('hg.admin')
233 233 def show(self, setting_id, format='html'):
234 234 """GET /admin/settings/setting_id: Show a specific item"""
235 235 # url('admin_setting', setting_id=ID)
236 236
237 237 @HasPermissionAllDecorator('hg.admin')
238 238 def edit(self, setting_id, format='html'):
239 239 """GET /admin/settings/setting_id/edit: Form to edit an existing item"""
240 240 # url('admin_edit_setting', setting_id=ID)
241 241
242 242 @NotAnonymous()
243 243 def my_account(self):
244 244 """
245 245 GET /_admin/my_account Displays info about my account
246 246 """
247 247 # url('admin_settings_my_account')
248 248
249 249 c.user = UserModel().get(c.rhodecode_user.user_id, cache=False)
250 250 all_repos = self.sa.query(Repository)\
251 251 .filter(Repository.user_id == c.user.user_id)\
252 252 .order_by(func.lower(Repository.repo_name))\
253 253 .all()
254 254
255 255 c.user_repos = ScmModel().get_repos(all_repos)
256 256
257 257 if c.user.username == 'default':
258 258 h.flash(_("You can't edit this user since it's"
259 259 " crucial for entire application"), category='warning')
260 260 return redirect(url('users'))
261 261
262 262 defaults = c.user.get_dict()
263 263 return htmlfill.render(
264 264 render('admin/users/user_edit_my_account.html'),
265 265 defaults=defaults,
266 266 encoding="UTF-8",
267 267 force_defaults=False
268 268 )
269 269
270 270 def my_account_update(self):
271 271 """PUT /_admin/my_account_update: Update an existing item"""
272 272 # Forms posted to this method should contain a hidden field:
273 273 # <input type="hidden" name="_method" value="PUT" />
274 274 # Or using helpers:
275 275 # h.form(url('admin_settings_my_account_update'),
276 276 # method='put')
277 277 # url('admin_settings_my_account_update', id=ID)
278 278 user_model = UserModel()
279 279 uid = c.rhodecode_user.user_id
280 280 _form = UserForm(edit=True, old_data={'user_id':uid,
281 281 'email':c.rhodecode_user.email})()
282 282 form_result = {}
283 283 try:
284 284 form_result = _form.to_python(dict(request.POST))
285 285 user_model.update_my_account(uid, form_result)
286 286 h.flash(_('Your account was updated successfully'),
287 287 category='success')
288 288
289 289 except formencode.Invalid, errors:
290 290 c.user = user_model.get(c.rhodecode_user.user_id, cache=False)
291 291 c.user = UserModel().get(c.rhodecode_user.user_id, cache=False)
292 292 all_repos = self.sa.query(Repository)\
293 293 .filter(Repository.user_id == c.user.user_id)\
294 294 .order_by(func.lower(Repository.repo_name))\
295 295 .all()
296 296 c.user_repos = ScmModel().get_repos(all_repos)
297 297
298 298 return htmlfill.render(
299 299 render('admin/users/user_edit_my_account.html'),
300 300 defaults=errors.value,
301 301 errors=errors.error_dict or {},
302 302 prefix_error=False,
303 303 encoding="UTF-8")
304 304 except Exception:
305 305 log.error(traceback.format_exc())
306 306 h.flash(_('error occurred during update of user %s') \
307 307 % form_result.get('username'), category='error')
308 308
309 309 return redirect(url('my_account'))
310 310
311 311 @NotAnonymous()
312 312 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
313 313 def create_repository(self):
314 314 """GET /_admin/create_repository: Form to create a new item"""
315 315 new_repo = request.GET.get('repo', '')
316 316 c.new_repo = h.repo_name_slug(new_repo)
317 317
318 318 return render('admin/repos/repo_add_create_repository.html')
319 319
320 320 def get_hg_ui_settings(self):
321 321 ret = self.sa.query(RhodeCodeUi).all()
322 322
323 323 if not ret:
324 324 raise Exception('Could not get application ui settings !')
325 325 settings = {}
326 326 for each in ret:
327 327 k = each.ui_key
328 328 v = each.ui_value
329 329 if k == '/':
330 330 k = 'root_path'
331 331
332 332 if k.find('.') != -1:
333 333 k = k.replace('.', '_')
334 334
335 335 if each.ui_section == 'hooks':
336 336 v = each.ui_active
337 337
338 338 settings[each.ui_section + '_' + k] = v
339 339
340 340 return settings
@@ -1,258 +1,278 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.files
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Files controller for RhodeCode
7 7
8 8 :created_on: Apr 21, 2010
9 9 :author: marcink
10 :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software; you can redistribute it and/or
14 14 # modify it under the terms of the GNU General Public License
15 15 # as published by the Free Software Foundation; version 2
16 16 # of the License or (at your opinion) any later version of the license.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program; if not, write to the Free Software
25 25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 26 # MA 02110-1301, USA.
27 27 import tempfile
28 28 import logging
29 29 import rhodecode.lib.helpers as h
30 30
31 31 from mercurial import archival
32 32
33 33 from pylons import request, response, session, tmpl_context as c, url
34 34 from pylons.i18n.translation import _
35 35 from pylons.controllers.util import redirect
36 36
37 37 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
38 38 from rhodecode.lib.base import BaseController, render
39 39 from rhodecode.lib.utils import EmptyChangeset
40 40 from rhodecode.model.scm import ScmModel
41 41
42 42 from vcs.exceptions import RepositoryError, ChangesetError, \
43 43 ChangesetDoesNotExistError, EmptyRepositoryError
44 44 from vcs.nodes import FileNode
45 45 from vcs.utils import diffs as differ
46 46
47 47 log = logging.getLogger(__name__)
48 48
49 49 class FilesController(BaseController):
50 50
51 51 @LoginRequired()
52 52 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
53 53 'repository.admin')
54 54 def __before__(self):
55 55 super(FilesController, self).__before__()
56 56 c.cut_off_limit = self.cut_off_limit
57 57
58 def __get_cs_or_redirect(self, rev, repo_name):
59 """
60 Safe way to get changeset if error occur it redirects to tip with
61 proper message
62
63 :param rev: revision to fetch
64 :param repo_name: repo name to redirect after
65 """
66
67 _repo = ScmModel().get_repo(c.repo_name)
68 try:
69 return _repo.get_changeset(rev)
70 except EmptyRepositoryError, e:
71 h.flash(_('There are no files yet'), category='warning')
72 redirect(h.url('summary_home', repo_name=repo_name))
73
74 except RepositoryError, e:
75 h.flash(str(e), category='warning')
76 redirect(h.url('files_home', repo_name=repo_name, revision='tip'))
77
58 78 def index(self, repo_name, revision, f_path):
59 hg_model = ScmModel()
60 c.repo = hg_model.get_repo(c.repo_name)
79 cs = self.__get_cs_or_redirect(revision, repo_name)
80 c.repo = ScmModel().get_repo(c.repo_name)
81
61 82 revision = request.POST.get('at_rev', None) or revision
62 83
63 84 def get_next_rev(cur):
64 85 max_rev = len(c.repo.revisions) - 1
65 86 r = cur + 1
66 87 if r > max_rev:
67 88 r = max_rev
68 89 return r
69 90
70 91 def get_prev_rev(cur):
71 92 r = cur - 1
72 93 return r
73 94
74 95 c.f_path = f_path
96 c.changeset = cs
97 cur_rev = c.changeset.revision
98 prev_rev = c.repo.get_changeset(get_prev_rev(cur_rev)).raw_id
99 next_rev = c.repo.get_changeset(get_next_rev(cur_rev)).raw_id
75 100
101 c.url_prev = url('files_home', repo_name=c.repo_name,
102 revision=prev_rev, f_path=f_path)
103 c.url_next = url('files_home', repo_name=c.repo_name,
104 revision=next_rev, f_path=f_path)
76 105
77 106 try:
78 c.changeset = c.repo.get_changeset(revision)
79 cur_rev = c.changeset.revision
80 prev_rev = c.repo.get_changeset(get_prev_rev(cur_rev)).raw_id
81 next_rev = c.repo.get_changeset(get_next_rev(cur_rev)).raw_id
82
83 c.url_prev = url('files_home', repo_name=c.repo_name,
84 revision=prev_rev, f_path=f_path)
85 c.url_next = url('files_home', repo_name=c.repo_name,
86 revision=next_rev, f_path=f_path)
87
88 try:
89 c.files_list = c.changeset.get_node(f_path)
90 c.file_history = self._get_history(c.repo, c.files_list, f_path)
91 except RepositoryError, e:
92 h.flash(str(e), category='warning')
93 redirect(h.url('files_home', repo_name=repo_name, revision=revision))
94
95 except EmptyRepositoryError, e:
96 h.flash(_('There are no files yet'), category='warning')
97 redirect(h.url('summary_home', repo_name=repo_name))
98
107 c.files_list = c.changeset.get_node(f_path)
108 c.file_history = self._get_history(c.repo, c.files_list, f_path)
99 109 except RepositoryError, e:
100 110 h.flash(str(e), category='warning')
101 redirect(h.url('files_home', repo_name=repo_name, revision='tip'))
102
111 redirect(h.url('files_home', repo_name=repo_name,
112 revision=revision))
103 113
104 114
105 115 return render('files/files.html')
106 116
107 117 def rawfile(self, repo_name, revision, f_path):
108 hg_model = ScmModel()
109 c.repo = hg_model.get_repo(c.repo_name)
110 file_node = c.repo.get_changeset(revision).get_node(f_path)
118 cs = self.__get_cs_or_redirect(revision, repo_name)
119 try:
120 file_node = cs.get_node(f_path)
121 except RepositoryError, e:
122 h.flash(str(e), category='warning')
123 redirect(h.url('files_home', repo_name=repo_name,
124 revision=cs.raw_id))
125
126 fname = f_path.split('/')[-1].encode('utf8', 'replace')
127
128 response.content_disposition = 'attachment; filename=%s' % fname
111 129 response.content_type = file_node.mimetype
112 response.content_disposition = 'attachment; filename=%s' \
113 % f_path.split('/')[-1]
114 130 return file_node.content
115 131
116 132 def raw(self, repo_name, revision, f_path):
117 hg_model = ScmModel()
118 c.repo = hg_model.get_repo(c.repo_name)
119 file_node = c.repo.get_changeset(revision).get_node(f_path)
133 cs = self.__get_cs_or_redirect(revision, repo_name)
134 try:
135 file_node = cs.get_node(f_path)
136 except RepositoryError, e:
137 h.flash(str(e), category='warning')
138 redirect(h.url('files_home', repo_name=repo_name,
139 revision=cs.raw_id))
140
120 141 response.content_type = 'text/plain'
121
122 142 return file_node.content
123 143
124 144 def annotate(self, repo_name, revision, f_path):
125 hg_model = ScmModel()
126 c.repo = hg_model.get_repo(c.repo_name)
127
145 cs = self.__get_cs_or_redirect(revision, repo_name)
128 146 try:
129 c.cs = c.repo.get_changeset(revision)
130 c.file = c.cs.get_node(f_path)
147 c.file = cs.get_node(f_path)
131 148 except RepositoryError, e:
132 149 h.flash(str(e), category='warning')
133 redirect(h.url('files_home', repo_name=repo_name, revision=revision))
150 redirect(h.url('files_home', repo_name=repo_name, revision=cs.raw_id))
134 151
135 c.file_history = self._get_history(c.repo, c.file, f_path)
136
152 c.file_history = self._get_history(ScmModel().get_repo(c.repo_name), c.file, f_path)
153 c.cs = cs
137 154 c.f_path = f_path
138 155
139 156 return render('files/files_annotate.html')
140 157
141 158 def archivefile(self, repo_name, revision, fileformat):
142 159 archive_specs = {
143 160 '.tar.bz2': ('application/x-tar', 'tbz2'),
144 161 '.tar.gz': ('application/x-tar', 'tgz'),
145 162 '.zip': ('application/zip', 'zip'),
146 163 }
147 164 if not archive_specs.has_key(fileformat):
148 165 return 'Unknown archive type %s' % fileformat
149 166
150 167 def read_in_chunks(file_object, chunk_size=1024 * 40):
151 168 """Lazy function (generator) to read a file piece by piece.
152 169 Default chunk size: 40k."""
153 170 while True:
154 171 data = file_object.read(chunk_size)
155 172 if not data:
156 173 break
157 174 yield data
158 175
159 176 archive = tempfile.TemporaryFile()
160 177 repo = ScmModel().get_repo(repo_name).repo
161 178 fname = '%s-%s%s' % (repo_name, revision, fileformat)
162 179 archival.archive(repo, archive, revision, archive_specs[fileformat][1],
163 180 prefix='%s-%s' % (repo_name, revision))
164 181 response.content_type = archive_specs[fileformat][0]
165 182 response.content_disposition = 'attachment; filename=%s' % fname
166 183 archive.seek(0)
167 184 return read_in_chunks(archive)
168 185
169 186 def diff(self, repo_name, f_path):
170 187 hg_model = ScmModel()
171 188 diff1 = request.GET.get('diff1')
172 189 diff2 = request.GET.get('diff2')
173 190 c.action = request.GET.get('diff')
174 191 c.no_changes = diff1 == diff2
175 192 c.f_path = f_path
176 193 c.repo = hg_model.get_repo(c.repo_name)
177 194
178 195 try:
179 196 if diff1 not in ['', None, 'None', '0' * 12, '0' * 40]:
180 197 c.changeset_1 = c.repo.get_changeset(diff1)
181 198 node1 = c.changeset_1.get_node(f_path)
182 199 else:
183 200 c.changeset_1 = EmptyChangeset()
184 201 node1 = FileNode('.', '', changeset=c.changeset_1)
185 202
186 203 if diff2 not in ['', None, 'None', '0' * 12, '0' * 40]:
187 204 c.changeset_2 = c.repo.get_changeset(diff2)
188 205 node2 = c.changeset_2.get_node(f_path)
189 206 else:
190 207 c.changeset_2 = EmptyChangeset()
191 208 node2 = FileNode('.', '', changeset=c.changeset_2)
192 209 except RepositoryError:
193 210 return redirect(url('files_home',
194 211 repo_name=c.repo_name, f_path=f_path))
195 212
196 213 f_udiff = differ.get_udiff(node1, node2)
197 214 diff = differ.DiffProcessor(f_udiff)
198 215
199 216 if c.action == 'download':
200 217 diff_name = '%s_vs_%s.diff' % (diff1, diff2)
201 218 response.content_type = 'text/plain'
202 219 response.content_disposition = 'attachment; filename=%s' \
203 220 % diff_name
221 if node1.is_binary or node2.is_binary:
222 return _('binary file changed')
204 223 return diff.raw_diff()
205 224
206 225 elif c.action == 'raw':
207 226 response.content_type = 'text/plain'
227 if node1.is_binary or node2.is_binary:
228 return _('binary file changed')
208 229 return diff.raw_diff()
209 230
210 231 elif c.action == 'diff':
211 232 if node1.size > self.cut_off_limit or node2.size > self.cut_off_limit:
212 233 c.cur_diff = _('Diff is to big to display')
234 elif node1.is_binary or node2.is_binary:
235 c.cur_diff = _('Binary file')
213 236 else:
214 237 c.cur_diff = diff.as_html()
215 238 else:
216 239 #default option
217 240 if node1.size > self.cut_off_limit or node2.size > self.cut_off_limit:
218 241 c.cur_diff = _('Diff is to big to display')
242 elif node1.is_binary or node2.is_binary:
243 c.cur_diff = _('Binary file')
219 244 else:
220 245 c.cur_diff = diff.as_html()
221 246
222 if not c.cur_diff: c.no_changes = True
247 if not c.cur_diff:
248 c.no_changes = True
223 249 return render('files/file_diff.html')
224 250
225 251 def _get_history(self, repo, node, f_path):
226 252 from vcs.nodes import NodeKind
227 253 if not node.kind is NodeKind.FILE:
228 254 return []
229 255 changesets = node.history
230 256 hist_l = []
231 257
232 258 changesets_group = ([], _("Changesets"))
233 259 branches_group = ([], _("Branches"))
234 260 tags_group = ([], _("Tags"))
235 261
236 262 for chs in changesets:
237 263 n_desc = 'r%s:%s' % (chs.revision, chs.short_id)
238 264 changesets_group[0].append((chs.raw_id, n_desc,))
239 265
240 266 hist_l.append(changesets_group)
241 267
242 268 for name, chs in c.repository_branches.items():
243 269 #chs = chs.split(':')[-1]
244 270 branches_group[0].append((chs, name),)
245 271 hist_l.append(branches_group)
246 272
247 273 for name, chs in c.repository_tags.items():
248 274 #chs = chs.split(':')[-1]
249 275 tags_group[0].append((chs, name),)
250 276 hist_l.append(tags_group)
251 277
252 278 return hist_l
253
254 # [
255 # ([("u1", "User1"), ("u2", "User2")], "Users"),
256 # ([("g1", "Group1"), ("g2", "Group2")], "Groups")
257 # ]
258
@@ -1,66 +1,66 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.home
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Home controller for Rhodecode
7 7
8 8 :created_on: Feb 18, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software; you can redistribute it and/or
14 14 # modify it under the terms of the GNU General Public License
15 15 # as published by the Free Software Foundation; version 2
16 16 # of the License or (at your opinion) any later version of the license.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program; if not, write to the Free Software
25 25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 26 # MA 02110-1301, USA.
27 27
28 28 import logging
29 29 from operator import itemgetter
30 30
31 31 from pylons import tmpl_context as c, request
32 32
33 33 from rhodecode.lib.auth import LoginRequired
34 34 from rhodecode.lib.base import BaseController, render
35 35 from rhodecode.model.scm import ScmModel
36 36
37 37 log = logging.getLogger(__name__)
38 38
39 39 class HomeController(BaseController):
40 40
41 41 @LoginRequired()
42 42 def __before__(self):
43 43 super(HomeController, self).__before__()
44 44
45 45 def index(self):
46 sortables = ['name', 'description', 'last_change', 'tip', 'contact']
46 sortables = ['name', 'description', 'last_change', 'tip', 'owner']
47 47 current_sort = request.GET.get('sort', 'name')
48 48 current_sort_slug = current_sort.replace('-', '')
49 49
50 50 if current_sort_slug not in sortables:
51 51 c.sort_by = 'name'
52 52 current_sort_slug = c.sort_by
53 53 else:
54 54 c.sort_by = current_sort
55 55 c.sort_slug = current_sort_slug
56 56 cached_repo_list = ScmModel().get_repos()
57 57
58 58 sort_key = current_sort_slug + '_sort'
59 59 if c.sort_by.startswith('-'):
60 60 c.repos_list = sorted(cached_repo_list, key=itemgetter(sort_key),
61 61 reverse=True)
62 62 else:
63 63 c.repos_list = sorted(cached_repo_list, key=itemgetter(sort_key),
64 64 reverse=False)
65 65
66 66 return render('/index.html')
@@ -1,97 +1,100 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.controllers.journal
4 4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Journal controller for pylons
7 7
8 8 :created_on: Nov 21, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software; you can redistribute it and/or
14 14 # modify it under the terms of the GNU General Public License
15 15 # as published by the Free Software Foundation; version 2
16 16 # of the License or (at your opinion) any later version of the license.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program; if not, write to the Free Software
25 25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 26 # MA 02110-1301, USA.
27 27
28 28 import logging
29 from sqlalchemy import or_
29 import traceback
30 30
31 31 from pylons import request, response, session, tmpl_context as c, url
32 from paste.httpexceptions import HTTPInternalServerError, HTTPBadRequest
33
34 from sqlalchemy import or_
32 35
33 36 from rhodecode.lib.auth import LoginRequired, NotAnonymous
34 37 from rhodecode.lib.base import BaseController, render
35 38 from rhodecode.lib.helpers import get_token
36 39 from rhodecode.model.db import UserLog, UserFollowing
37 40 from rhodecode.model.scm import ScmModel
38 41
39 from paste.httpexceptions import HTTPInternalServerError
40
41 42 log = logging.getLogger(__name__)
42 43
43 44 class JournalController(BaseController):
44 45
45 46
46 47 @LoginRequired()
47 48 @NotAnonymous()
48 49 def __before__(self):
49 50 super(JournalController, self).__before__()
50 51
51 52 def index(self):
52 53 # Return a rendered template
53 54
54 55 c.following = self.sa.query(UserFollowing)\
55 56 .filter(UserFollowing.user_id == c.rhodecode_user.user_id).all()
56 57
57 58 repo_ids = [x.follows_repository.repo_id for x in c.following
58 59 if x.follows_repository is not None]
59 60 user_ids = [x.follows_user.user_id for x in c.following
60 61 if x.follows_user is not None]
61 62
62 63 c.journal = self.sa.query(UserLog)\
63 64 .filter(or_(
64 65 UserLog.repository_id.in_(repo_ids),
65 66 UserLog.user_id.in_(user_ids),
66 67 ))\
67 68 .order_by(UserLog.action_date.desc())\
68 69 .limit(20)\
69 70 .all()
70 71 return render('/journal.html')
71 72
72 73 def toggle_following(self):
73 74
74 75 if request.POST.get('auth_token') == get_token():
75 76 scm_model = ScmModel()
76 77
77 78 user_id = request.POST.get('follows_user_id')
78 79 if user_id:
79 80 try:
80 81 scm_model.toggle_following_user(user_id,
81 82 c.rhodecode_user.user_id)
82 83 return 'ok'
83 84 except:
85 log.error(traceback.format_exc())
84 86 raise HTTPInternalServerError()
85 87
86 88 repo_id = request.POST.get('follows_repo_id')
87 89 if repo_id:
88 90 try:
89 91 scm_model.toggle_following_repo(repo_id,
90 92 c.rhodecode_user.user_id)
91 93 return 'ok'
92 94 except:
95 log.error(traceback.format_exc())
93 96 raise HTTPInternalServerError()
94 97
95 98
96 99
97 raise HTTPInternalServerError()
100 raise HTTPBadRequest()
@@ -1,29 +1,46 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.__init__
4 4 ~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Some simple helper functions
7 7
8 8 :created_on: Jan 5, 2011
9 9 :author: marcink
10 10 :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software; you can redistribute it and/or
14 14 # modify it under the terms of the GNU General Public License
15 15 # as published by the Free Software Foundation; version 2
16 16 # of the License or (at your opinion) any later version of the license.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program; if not, write to the Free Software
25 25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 26 # MA 02110-1301, USA.
27 27
28 28 def str2bool(v):
29 return v.lower() in ["yes", "true", "t", "1"] if v else None
29 if isinstance(v, (str, unicode)):
30 obj = v.strip().lower()
31 if obj in ['true', 'yes', 'on', 'y', 't', '1']:
32 return True
33 elif obj in ['false', 'no', 'off', 'n', 'f', '0']:
34 return False
35 else:
36 raise ValueError("String is not true/false: %r" % obj)
37 return bool(obj)
38
39 def generate_api_key(username, salt=None):
40 from tempfile import _RandomNameSequence
41 import hashlib
42
43 if salt is None:
44 salt = _RandomNameSequence().next()
45
46 return hashlib.sha1(username + salt).hexdigest()
@@ -1,569 +1,614 b''
1 #!/usr/bin/env python
2 # encoding: utf-8
3 # authentication and permission libraries
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
5 #
1 # -*- coding: utf-8 -*-
2 """
3 rhodecode.lib.auth
4 ~~~~~~~~~~~~~~~~~~
5
6 authentication and permission libraries
7
8 :created_on: Apr 4, 2010
9 :copyright: (c) 2010 by marcink.
10 :license: LICENSE_NAME, see LICENSE_FILE for more details.
11 """
6 12 # This program is free software; you can redistribute it and/or
7 13 # modify it under the terms of the GNU General Public License
8 14 # as published by the Free Software Foundation; version 2
9 15 # of the License or (at your opinion) any later version of the license.
10 16 #
11 17 # This program is distributed in the hope that it will be useful,
12 18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 20 # GNU General Public License for more details.
15 21 #
16 22 # You should have received a copy of the GNU General Public License
17 23 # along with this program; if not, write to the Free Software
18 24 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 25 # MA 02110-1301, USA.
20 """
21 Created on April 4, 2010
22 26
23 @author: marcink
24 """
27 import random
28 import logging
29 import traceback
30
31 from decorator import decorator
32
25 33 from pylons import config, session, url, request
26 34 from pylons.controllers.util import abort, redirect
27 from rhodecode.lib.exceptions import *
35 from pylons.i18n.translation import _
36
37 from rhodecode import __platform__
38
39 if __platform__ == 'Windows':
40 from hashlib import sha256
41 if __platform__ in ('Linux', 'Darwin'):
42 import bcrypt
43
44 from rhodecode.lib import str2bool
45 from rhodecode.lib.exceptions import LdapPasswordError, LdapUsernameError
28 46 from rhodecode.lib.utils import get_repo_slug
29 47 from rhodecode.lib.auth_ldap import AuthLdap
48
30 49 from rhodecode.model import meta
31 50 from rhodecode.model.user import UserModel
32 from rhodecode.model.caching_query import FromCache
33 from rhodecode.model.db import User, RepoToPerm, Repository, Permission, \
34 UserToPerm
35 import bcrypt
36 from decorator import decorator
37 import logging
38 import random
39 import traceback
51 from rhodecode.model.db import Permission, RepoToPerm, Repository, \
52 User, UserToPerm
53
40 54
41 55 log = logging.getLogger(__name__)
42 56
43 57 class PasswordGenerator(object):
44 58 """This is a simple class for generating password from
45 59 different sets of characters
46 60 usage:
47 61 passwd_gen = PasswordGenerator()
48 62 #print 8-letter password containing only big and small letters of alphabet
49 63 print passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
50 64 """
51 65 ALPHABETS_NUM = r'''1234567890'''#[0]
52 66 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''#[1]
53 67 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''#[2]
54 68 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?''' #[3]
55 69 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM + ALPHABETS_SPECIAL#[4]
56 70 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM#[5]
57 71 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
58 72 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM#[6]
59 73 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM#[7]
60 74
61 75 def __init__(self, passwd=''):
62 76 self.passwd = passwd
63 77
64 78 def gen_password(self, len, type):
65 79 self.passwd = ''.join([random.choice(type) for _ in xrange(len)])
66 80 return self.passwd
67 81
82 class RhodeCodeCrypto(object):
83
84 @classmethod
85 def hash_string(cls, str_):
86 """
87 Cryptographic function used for password hashing based on pybcrypt
88 or pycrypto in windows
89
90 :param password: password to hash
91 """
92 if __platform__ == 'Windows':
93 return sha256(str_).hexdigest()
94 elif __platform__ in ('Linux', 'Darwin'):
95 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
96 else:
97 raise Exception('Unknown or unsupported platform %s' % __platform__)
98
99 @classmethod
100 def hash_check(cls, password, hashed):
101 """
102 Checks matching password with it's hashed value, runs different
103 implementation based on platform it runs on
104
105 :param password: password
106 :param hashed: password in hashed form
107 """
108
109 if __platform__ == 'Windows':
110 return sha256(password).hexdigest() == hashed
111 elif __platform__ in ('Linux', 'Darwin'):
112 return bcrypt.hashpw(password, hashed) == hashed
113 else:
114 raise Exception('Unknown or unsupported platform %s' % __platform__)
115
68 116
69 117 def get_crypt_password(password):
70 """Cryptographic function used for password hashing based on sha1
71 :param password: password to hash
72 """
73 return bcrypt.hashpw(password, bcrypt.gensalt(10))
118 return RhodeCodeCrypto.hash_string(password)
74 119
75 120 def check_password(password, hashed):
76 return bcrypt.hashpw(password, hashed) == hashed
121 return RhodeCodeCrypto.hash_check(password, hashed)
77 122
78 123 def authfunc(environ, username, password):
79 124 """
80 125 Dummy authentication function used in Mercurial/Git/ and access control,
81 126
82 127 :param environ: needed only for using in Basic auth
83 128 """
84 129 return authenticate(username, password)
85 130
86 131
87 132 def authenticate(username, password):
88 133 """
89 134 Authentication function used for access control,
90 135 firstly checks for db authentication then if ldap is enabled for ldap
91 136 authentication, also creates ldap user if not in database
92 137
93 138 :param username: username
94 139 :param password: password
95 140 """
96 141 user_model = UserModel()
97 142 user = user_model.get_by_username(username, cache=False)
98 143
99 144 log.debug('Authenticating user using RhodeCode account')
100 145 if user is not None and user.is_ldap is False:
101 146 if user.active:
102 147
103 148 if user.username == 'default' and user.active:
104 149 log.info('user %s authenticated correctly as anonymous user',
105 150 username)
106 151 return True
107 152
108 153 elif user.username == username and check_password(password, user.password):
109 154 log.info('user %s authenticated correctly', username)
110 155 return True
111 156 else:
112 157 log.warning('user %s is disabled', username)
113 158
114 159 else:
115 160 log.debug('Regular authentication failed')
116 161 user_obj = user_model.get_by_username(username, cache=False,
117 162 case_insensitive=True)
118 163
119 164 if user_obj is not None and user_obj.is_ldap is False:
120 165 log.debug('this user already exists as non ldap')
121 166 return False
122 167
123 168 from rhodecode.model.settings import SettingsModel
124 169 ldap_settings = SettingsModel().get_ldap_settings()
125 170
126 171 #======================================================================
127 172 # FALLBACK TO LDAP AUTH IN ENABLE
128 173 #======================================================================
129 if ldap_settings.get('ldap_active', False):
174 if str2bool(ldap_settings.get('ldap_active')):
130 175 log.debug("Authenticating user using ldap")
131 176 kwargs = {
132 177 'server':ldap_settings.get('ldap_host', ''),
133 178 'base_dn':ldap_settings.get('ldap_base_dn', ''),
134 179 'port':ldap_settings.get('ldap_port'),
135 180 'bind_dn':ldap_settings.get('ldap_dn_user'),
136 181 'bind_pass':ldap_settings.get('ldap_dn_pass'),
137 'use_ldaps':ldap_settings.get('ldap_ldaps'),
182 'use_ldaps':str2bool(ldap_settings.get('ldap_ldaps')),
138 183 'ldap_version':3,
139 184 }
140 185 log.debug('Checking for ldap authentication')
141 186 try:
142 187 aldap = AuthLdap(**kwargs)
143 188 res = aldap.authenticate_ldap(username, password)
144 189 log.debug('Got ldap response %s', res)
145 190
146 191 if user_model.create_ldap(username, password):
147 192 log.info('created new ldap user')
148 193
149 194 return True
150 195 except (LdapUsernameError, LdapPasswordError,):
151 196 pass
152 197 except (Exception,):
153 198 log.error(traceback.format_exc())
154 199 pass
155 200 return False
156 201
157 202 class AuthUser(object):
158 203 """
159 204 A simple object that handles a mercurial username for authentication
160 205 """
161 206 def __init__(self):
162 207 self.username = 'None'
163 208 self.name = ''
164 209 self.lastname = ''
165 210 self.email = ''
166 211 self.user_id = None
167 212 self.is_authenticated = False
168 213 self.is_admin = False
169 214 self.permissions = {}
170 215
171 216 def __repr__(self):
172 217 return "<AuthUser('id:%s:%s')>" % (self.user_id, self.username)
173 218
174 219 def set_available_permissions(config):
175 220 """
176 221 This function will propagate pylons globals with all available defined
177 222 permission given in db. We don't wannt to check each time from db for new
178 223 permissions since adding a new permission also requires application restart
179 224 ie. to decorate new views with the newly created permission
180 225 :param config:
181 226 """
182 227 log.info('getting information about all available permissions')
183 228 try:
184 229 sa = meta.Session()
185 230 all_perms = sa.query(Permission).all()
186 231 except:
187 232 pass
188 233 finally:
189 234 meta.Session.remove()
190 235
191 236 config['available_permissions'] = [x.permission_name for x in all_perms]
192 237
193 238 def set_base_path(config):
194 239 config['base_path'] = config['pylons.app_globals'].base_path
195 240
196 241
197 242 def fill_perms(user):
198 243 """
199 244 Fills user permission attribute with permissions taken from database
200 245 :param user:
201 246 """
202 247
203 248 sa = meta.Session()
204 249 user.permissions['repositories'] = {}
205 250 user.permissions['global'] = set()
206 251
207 252 #===========================================================================
208 253 # fetch default permissions
209 254 #===========================================================================
210 255 default_user = UserModel().get_by_username('default', cache=True)
211 256
212 257 default_perms = sa.query(RepoToPerm, Repository, Permission)\
213 258 .join((Repository, RepoToPerm.repository_id == Repository.repo_id))\
214 259 .join((Permission, RepoToPerm.permission_id == Permission.permission_id))\
215 260 .filter(RepoToPerm.user == default_user).all()
216 261
217 262 if user.is_admin:
218 263 #=======================================================================
219 264 # #admin have all default rights set to admin
220 265 #=======================================================================
221 266 user.permissions['global'].add('hg.admin')
222 267
223 268 for perm in default_perms:
224 269 p = 'repository.admin'
225 270 user.permissions['repositories'][perm.RepoToPerm.repository.repo_name] = p
226 271
227 272 else:
228 273 #=======================================================================
229 274 # set default permissions
230 275 #=======================================================================
231 276
232 277 #default global
233 278 default_global_perms = sa.query(UserToPerm)\
234 279 .filter(UserToPerm.user == sa.query(User)\
235 280 .filter(User.username == 'default').one())
236 281
237 282 for perm in default_global_perms:
238 283 user.permissions['global'].add(perm.permission.permission_name)
239 284
240 285 #default repositories
241 286 for perm in default_perms:
242 287 if perm.Repository.private and not perm.Repository.user_id == user.user_id:
243 288 #disable defaults for private repos,
244 289 p = 'repository.none'
245 290 elif perm.Repository.user_id == user.user_id:
246 291 #set admin if owner
247 292 p = 'repository.admin'
248 293 else:
249 294 p = perm.Permission.permission_name
250 295
251 296 user.permissions['repositories'][perm.RepoToPerm.repository.repo_name] = p
252 297
253 298 #=======================================================================
254 299 # #overwrite default with user permissions if any
255 300 #=======================================================================
256 301 user_perms = sa.query(RepoToPerm, Permission, Repository)\
257 302 .join((Repository, RepoToPerm.repository_id == Repository.repo_id))\
258 303 .join((Permission, RepoToPerm.permission_id == Permission.permission_id))\
259 304 .filter(RepoToPerm.user_id == user.user_id).all()
260 305
261 306 for perm in user_perms:
262 307 if perm.Repository.user_id == user.user_id:#set admin if owner
263 308 p = 'repository.admin'
264 309 else:
265 310 p = perm.Permission.permission_name
266 311 user.permissions['repositories'][perm.RepoToPerm.repository.repo_name] = p
267 312 meta.Session.remove()
268 313 return user
269 314
270 315 def get_user(session):
271 316 """
272 317 Gets user from session, and wraps permissions into user
273 318 :param session:
274 319 """
275 320 user = session.get('rhodecode_user', AuthUser())
276 321 #if the user is not logged in we check for anonymous access
277 322 #if user is logged and it's a default user check if we still have anonymous
278 323 #access enabled
279 324 if user.user_id is None or user.username == 'default':
280 325 anonymous_user = UserModel().get_by_username('default', cache=True)
281 326 if anonymous_user.active is True:
282 327 #then we set this user is logged in
283 328 user.is_authenticated = True
284 329 user.user_id = anonymous_user.user_id
285 330 else:
286 331 user.is_authenticated = False
287 332
288 333 if user.is_authenticated:
289 334 user = UserModel().fill_data(user)
290 335
291 336 user = fill_perms(user)
292 337 session['rhodecode_user'] = user
293 338 session.save()
294 339 return user
295 340
296 341 #===============================================================================
297 342 # CHECK DECORATORS
298 343 #===============================================================================
299 344 class LoginRequired(object):
300 345 """Must be logged in to execute this function else
301 346 redirect to login page"""
302 347
303 348 def __call__(self, func):
304 349 return decorator(self.__wrapper, func)
305 350
306 351 def __wrapper(self, func, *fargs, **fkwargs):
307 352 user = session.get('rhodecode_user', AuthUser())
308 353 log.debug('Checking login required for user:%s', user.username)
309 354 if user.is_authenticated:
310 355 log.debug('user %s is authenticated', user.username)
311 356 return func(*fargs, **fkwargs)
312 357 else:
313 358 log.warn('user %s not authenticated', user.username)
314 359
315 360 p = ''
316 361 if request.environ.get('SCRIPT_NAME') != '/':
317 362 p += request.environ.get('SCRIPT_NAME')
318 363
319 364 p += request.environ.get('PATH_INFO')
320 365 if request.environ.get('QUERY_STRING'):
321 366 p += '?' + request.environ.get('QUERY_STRING')
322 367
323 368 log.debug('redirecting to login page with %s', p)
324 369 return redirect(url('login_home', came_from=p))
325 370
326 371 class NotAnonymous(object):
327 372 """Must be logged in to execute this function else
328 373 redirect to login page"""
329 374
330 375 def __call__(self, func):
331 376 return decorator(self.__wrapper, func)
332 377
333 378 def __wrapper(self, func, *fargs, **fkwargs):
334 379 user = session.get('rhodecode_user', AuthUser())
335 380 log.debug('Checking if user is not anonymous')
336 381
337 382 anonymous = user.username == 'default'
338 383
339 384 if anonymous:
340 385 p = ''
341 386 if request.environ.get('SCRIPT_NAME') != '/':
342 387 p += request.environ.get('SCRIPT_NAME')
343 388
344 389 p += request.environ.get('PATH_INFO')
345 390 if request.environ.get('QUERY_STRING'):
346 391 p += '?' + request.environ.get('QUERY_STRING')
347 392 return redirect(url('login_home', came_from=p))
348 393 else:
349 394 return func(*fargs, **fkwargs)
350 395
351 396 class PermsDecorator(object):
352 397 """Base class for decorators"""
353 398
354 399 def __init__(self, *required_perms):
355 400 available_perms = config['available_permissions']
356 401 for perm in required_perms:
357 402 if perm not in available_perms:
358 403 raise Exception("'%s' permission is not defined" % perm)
359 404 self.required_perms = set(required_perms)
360 405 self.user_perms = None
361 406
362 407 def __call__(self, func):
363 408 return decorator(self.__wrapper, func)
364 409
365 410
366 411 def __wrapper(self, func, *fargs, **fkwargs):
367 412 # _wrapper.__name__ = func.__name__
368 413 # _wrapper.__dict__.update(func.__dict__)
369 414 # _wrapper.__doc__ = func.__doc__
370 415 self.user = session.get('rhodecode_user', AuthUser())
371 416 self.user_perms = self.user.permissions
372 417 log.debug('checking %s permissions %s for %s %s',
373 418 self.__class__.__name__, self.required_perms, func.__name__,
374 419 self.user)
375 420
376 421 if self.check_permissions():
377 422 log.debug('Permission granted for %s %s', func.__name__, self.user)
378 423
379 424 return func(*fargs, **fkwargs)
380 425
381 426 else:
382 427 log.warning('Permission denied for %s %s', func.__name__, self.user)
383 428 #redirect with forbidden ret code
384 429 return abort(403)
385 430
386 431
387 432
388 433 def check_permissions(self):
389 434 """Dummy function for overriding"""
390 435 raise Exception('You have to write this function in child class')
391 436
392 437 class HasPermissionAllDecorator(PermsDecorator):
393 438 """Checks for access permission for all given predicates. All of them
394 439 have to be meet in order to fulfill the request
395 440 """
396 441
397 442 def check_permissions(self):
398 443 if self.required_perms.issubset(self.user_perms.get('global')):
399 444 return True
400 445 return False
401 446
402 447
403 448 class HasPermissionAnyDecorator(PermsDecorator):
404 449 """Checks for access permission for any of given predicates. In order to
405 450 fulfill the request any of predicates must be meet
406 451 """
407 452
408 453 def check_permissions(self):
409 454 if self.required_perms.intersection(self.user_perms.get('global')):
410 455 return True
411 456 return False
412 457
413 458 class HasRepoPermissionAllDecorator(PermsDecorator):
414 459 """Checks for access permission for all given predicates for specific
415 460 repository. All of them have to be meet in order to fulfill the request
416 461 """
417 462
418 463 def check_permissions(self):
419 464 repo_name = get_repo_slug(request)
420 465 try:
421 466 user_perms = set([self.user_perms['repositories'][repo_name]])
422 467 except KeyError:
423 468 return False
424 469 if self.required_perms.issubset(user_perms):
425 470 return True
426 471 return False
427 472
428 473
429 474 class HasRepoPermissionAnyDecorator(PermsDecorator):
430 475 """Checks for access permission for any of given predicates for specific
431 476 repository. In order to fulfill the request any of predicates must be meet
432 477 """
433 478
434 479 def check_permissions(self):
435 480 repo_name = get_repo_slug(request)
436 481
437 482 try:
438 483 user_perms = set([self.user_perms['repositories'][repo_name]])
439 484 except KeyError:
440 485 return False
441 486 if self.required_perms.intersection(user_perms):
442 487 return True
443 488 return False
444 489 #===============================================================================
445 490 # CHECK FUNCTIONS
446 491 #===============================================================================
447 492
448 493 class PermsFunction(object):
449 494 """Base function for other check functions"""
450 495
451 496 def __init__(self, *perms):
452 497 available_perms = config['available_permissions']
453 498
454 499 for perm in perms:
455 500 if perm not in available_perms:
456 501 raise Exception("'%s' permission in not defined" % perm)
457 502 self.required_perms = set(perms)
458 503 self.user_perms = None
459 504 self.granted_for = ''
460 505 self.repo_name = None
461 506
462 507 def __call__(self, check_Location=''):
463 508 user = session.get('rhodecode_user', False)
464 509 if not user:
465 510 return False
466 511 self.user_perms = user.permissions
467 512 self.granted_for = user.username
468 513 log.debug('checking %s %s %s', self.__class__.__name__,
469 514 self.required_perms, user)
470 515
471 516 if self.check_permissions():
472 517 log.debug('Permission granted for %s @ %s %s', self.granted_for,
473 518 check_Location, user)
474 519 return True
475 520
476 521 else:
477 522 log.warning('Permission denied for %s @ %s %s', self.granted_for,
478 523 check_Location, user)
479 524 return False
480 525
481 526 def check_permissions(self):
482 527 """Dummy function for overriding"""
483 528 raise Exception('You have to write this function in child class')
484 529
485 530 class HasPermissionAll(PermsFunction):
486 531 def check_permissions(self):
487 532 if self.required_perms.issubset(self.user_perms.get('global')):
488 533 return True
489 534 return False
490 535
491 536 class HasPermissionAny(PermsFunction):
492 537 def check_permissions(self):
493 538 if self.required_perms.intersection(self.user_perms.get('global')):
494 539 return True
495 540 return False
496 541
497 542 class HasRepoPermissionAll(PermsFunction):
498 543
499 544 def __call__(self, repo_name=None, check_Location=''):
500 545 self.repo_name = repo_name
501 546 return super(HasRepoPermissionAll, self).__call__(check_Location)
502 547
503 548 def check_permissions(self):
504 549 if not self.repo_name:
505 550 self.repo_name = get_repo_slug(request)
506 551
507 552 try:
508 553 self.user_perms = set([self.user_perms['repositories']\
509 554 [self.repo_name]])
510 555 except KeyError:
511 556 return False
512 557 self.granted_for = self.repo_name
513 558 if self.required_perms.issubset(self.user_perms):
514 559 return True
515 560 return False
516 561
517 562 class HasRepoPermissionAny(PermsFunction):
518 563
519 564 def __call__(self, repo_name=None, check_Location=''):
520 565 self.repo_name = repo_name
521 566 return super(HasRepoPermissionAny, self).__call__(check_Location)
522 567
523 568 def check_permissions(self):
524 569 if not self.repo_name:
525 570 self.repo_name = get_repo_slug(request)
526 571
527 572 try:
528 573 self.user_perms = set([self.user_perms['repositories']\
529 574 [self.repo_name]])
530 575 except KeyError:
531 576 return False
532 577 self.granted_for = self.repo_name
533 578 if self.required_perms.intersection(self.user_perms):
534 579 return True
535 580 return False
536 581
537 582 #===============================================================================
538 583 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
539 584 #===============================================================================
540 585
541 586 class HasPermissionAnyMiddleware(object):
542 587 def __init__(self, *perms):
543 588 self.required_perms = set(perms)
544 589
545 590 def __call__(self, user, repo_name):
546 591 usr = AuthUser()
547 592 usr.user_id = user.user_id
548 593 usr.username = user.username
549 594 usr.is_admin = user.admin
550 595
551 596 try:
552 597 self.user_perms = set([fill_perms(usr)\
553 598 .permissions['repositories'][repo_name]])
554 599 except:
555 600 self.user_perms = set()
556 601 self.granted_for = ''
557 602 self.username = user.username
558 603 self.repo_name = repo_name
559 604 return self.check_permissions()
560 605
561 606 def check_permissions(self):
562 607 log.debug('checking mercurial protocol '
563 608 'permissions for user:%s repository:%s',
564 609 self.username, self.repo_name)
565 610 if self.required_perms.intersection(self.user_perms):
566 611 log.debug('permission granted')
567 612 return True
568 613 log.debug('permission denied')
569 614 return False
@@ -1,104 +1,104 b''
1 1 #!/usr/bin/env python
2 2 # encoding: utf-8
3 3 # ldap authentication lib
4 # Copyright (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
4 # Copyright (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
5 5 #
6 6 # This program is free software; you can redistribute it and/or
7 7 # modify it under the terms of the GNU General Public License
8 8 # as published by the Free Software Foundation; version 2
9 9 # of the License or (at your opinion) any later version of the license.
10 10 #
11 11 # This program is distributed in the hope that it will be useful,
12 12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 14 # GNU General Public License for more details.
15 15 #
16 16 # You should have received a copy of the GNU General Public License
17 17 # along with this program; if not, write to the Free Software
18 18 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 19 # MA 02110-1301, USA.
20 20 """
21 21 Created on Nov 17, 2010
22 22
23 23 @author: marcink
24 24 """
25 25
26 26 from rhodecode.lib.exceptions import *
27 27 import logging
28 28
29 29 log = logging.getLogger(__name__)
30 30
31 31 try:
32 32 import ldap
33 33 except ImportError:
34 34 pass
35 35
36 36 class AuthLdap(object):
37 37
38 38 def __init__(self, server, base_dn, port=389, bind_dn='', bind_pass='',
39 39 use_ldaps=False, ldap_version=3):
40 40 self.ldap_version = ldap_version
41 41 if use_ldaps:
42 42 port = port or 689
43 43 self.LDAP_USE_LDAPS = use_ldaps
44 44 self.LDAP_SERVER_ADDRESS = server
45 45 self.LDAP_SERVER_PORT = port
46 46
47 47 #USE FOR READ ONLY BIND TO LDAP SERVER
48 48 self.LDAP_BIND_DN = bind_dn
49 49 self.LDAP_BIND_PASS = bind_pass
50 50
51 51 ldap_server_type = 'ldap'
52 52 if self.LDAP_USE_LDAPS:ldap_server_type = ldap_server_type + 's'
53 53 self.LDAP_SERVER = "%s://%s:%s" % (ldap_server_type,
54 54 self.LDAP_SERVER_ADDRESS,
55 55 self.LDAP_SERVER_PORT)
56 56
57 57 self.BASE_DN = base_dn
58 58
59 59 def authenticate_ldap(self, username, password):
60 60 """Authenticate a user via LDAP and return his/her LDAP properties.
61 61
62 62 Raises AuthenticationError if the credentials are rejected, or
63 63 EnvironmentError if the LDAP server can't be reached.
64 64
65 65 :param username: username
66 66 :param password: password
67 67 """
68 68
69 69 from rhodecode.lib.helpers import chop_at
70 70
71 71 uid = chop_at(username, "@%s" % self.LDAP_SERVER_ADDRESS)
72 72
73 73 if "," in username:
74 74 raise LdapUsernameError("invalid character in username: ,")
75 75 try:
76 76 ldap.set_option(ldap.OPT_X_TLS_CACERTDIR, '/etc/openldap/cacerts')
77 77 ldap.set_option(ldap.OPT_NETWORK_TIMEOUT, 10)
78 78 server = ldap.initialize(self.LDAP_SERVER)
79 79 if self.ldap_version == 2:
80 80 server.protocol = ldap.VERSION2
81 81 else:
82 82 server.protocol = ldap.VERSION3
83 83
84 84 if self.LDAP_BIND_DN and self.LDAP_BIND_PASS:
85 85 server.simple_bind_s(self.LDAP_BIND_DN, self.LDAP_BIND_PASS)
86 86
87 87 dn = self.BASE_DN % {'user':uid}
88 88 log.debug("Authenticating %r at %s", dn, self.LDAP_SERVER)
89 89 server.simple_bind_s(dn, password)
90 90
91 91 properties = server.search_s(dn, ldap.SCOPE_SUBTREE)
92 92 if not properties:
93 93 raise ldap.NO_SUCH_OBJECT()
94 94 except ldap.NO_SUCH_OBJECT, e:
95 95 log.debug("LDAP says no such user '%s' (%s)", uid, username)
96 96 raise LdapUsernameError()
97 97 except ldap.INVALID_CREDENTIALS, e:
98 98 log.debug("LDAP rejected password for user '%s' (%s)", uid, username)
99 99 raise LdapPasswordError()
100 100 except ldap.SERVER_DOWN, e:
101 101 raise LdapConnectionError("LDAP can't access authentication server")
102 102
103 103 return properties[0]
104 104
@@ -1,493 +1,477 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.lib.db_manage
4 4 ~~~~~~~~~~~~~~~~~~~~~~~
5 5
6 6 Database creation, and setup module for RhodeCode. Used for creation
7 7 of database as well as for migration operations
8 8
9 9 :created_on: Apr 10, 2010
10 10 :author: marcink
11 11 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
12 12 :license: GPLv3, see COPYING for more details.
13 13 """
14 14 # This program is free software; you can redistribute it and/or
15 15 # modify it under the terms of the GNU General Public License
16 16 # as published by the Free Software Foundation; version 2
17 17 # of the License or (at your opinion) any later version of the license.
18 18 #
19 19 # This program is distributed in the hope that it will be useful,
20 20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 22 # GNU General Public License for more details.
23 23 #
24 24 # You should have received a copy of the GNU General Public License
25 25 # along with this program; if not, write to the Free Software
26 26 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
27 27 # MA 02110-1301, USA.
28 28
29 29 import os
30 30 import sys
31 31 import uuid
32 32 import logging
33 33 from os.path import dirname as dn, join as jn
34 34
35 35 from rhodecode import __dbversion__
36 36 from rhodecode.model import meta
37 37
38 38 from rhodecode.lib.auth import get_crypt_password
39 39 from rhodecode.lib.utils import ask_ok
40 40 from rhodecode.model import init_model
41 41 from rhodecode.model.db import User, Permission, RhodeCodeUi, RhodeCodeSettings, \
42 42 UserToPerm, DbMigrateVersion
43 43
44 44 from sqlalchemy.engine import create_engine
45 45
46 46 log = logging.getLogger(__name__)
47 47
48 48 class DbManage(object):
49 49 def __init__(self, log_sql, dbconf, root, tests=False):
50 50 self.dbname = dbconf.split('/')[-1]
51 51 self.tests = tests
52 52 self.root = root
53 53 self.dburi = dbconf
54 54 self.log_sql = log_sql
55 55 self.db_exists = False
56 56 self.init_db()
57 57
58 58 def init_db(self):
59 59 engine = create_engine(self.dburi, echo=self.log_sql)
60 60 init_model(engine)
61 61 self.sa = meta.Session()
62 62
63 def check_for_db(self, override):
64 db_path = jn(self.root, self.dbname)
65 if self.dburi.startswith('sqlite'):
66 log.info('checking for existing db in %s', db_path)
67 if os.path.isfile(db_path):
68
69 self.db_exists = True
70 if not override:
71 raise Exception('database already exists')
72 return 'sqlite'
73 if self.dburi.startswith('postgresql'):
74 self.db_exists = True
75 return 'postgresql'
76
77
78 63 def create_tables(self, override=False):
79 64 """Create a auth database
80 65 """
81 66
82 db_type = self.check_for_db(override)
83 if self.db_exists:
84 log.info("database exist and it's going to be destroyed")
85 if self.tests:
86 destroy = True
87 else:
88 destroy = ask_ok('Are you sure to destroy old database ? [y/n]')
89 if not destroy:
90 sys.exit()
91 if self.db_exists and destroy:
92 if db_type == 'sqlite':
93 os.remove(jn(self.root, self.dbname))
94 if db_type == 'postgresql':
95 meta.Base.metadata.drop_all()
67 log.info("Any existing database is going to be destroyed")
68 if self.tests:
69 destroy = True
70 else:
71 destroy = ask_ok('Are you sure to destroy old database ? [y/n]')
72 if not destroy:
73 sys.exit()
74 if destroy:
75 meta.Base.metadata.drop_all()
96 76
97 77 checkfirst = not override
98 78 meta.Base.metadata.create_all(checkfirst=checkfirst)
99 79 log.info('Created tables for %s', self.dbname)
100 80
101 81
102 82
103 83 def set_db_version(self):
104 84 try:
105 85 ver = DbMigrateVersion()
106 86 ver.version = __dbversion__
107 87 ver.repository_id = 'rhodecode_db_migrations'
108 88 ver.repository_path = 'versions'
109 89 self.sa.add(ver)
110 90 self.sa.commit()
111 91 except:
112 92 self.sa.rollback()
113 93 raise
114 94 log.info('db version set to: %s', __dbversion__)
115 95
116 96
117 97 def upgrade(self):
118 98 """Upgrades given database schema to given revision following
119 99 all needed steps, to perform the upgrade
120 100
121 101 :param revision: revision to upgrade to
122 102 """
123 103
124 104 from rhodecode.lib.dbmigrate.migrate.versioning import api
125 105 from rhodecode.lib.dbmigrate.migrate.exceptions import \
126 106 DatabaseNotControlledError
127 107
128 108 upgrade = ask_ok('You are about to perform database upgrade, make '
129 109 'sure You backed up your database before. '
130 110 'Continue ? [y/n]')
131 111 if not upgrade:
132 112 sys.exit('Nothing done')
133 113
134 114 repository_path = jn(dn(dn(dn(os.path.realpath(__file__)))),
135 115 'rhodecode/lib/dbmigrate')
136 116 db_uri = self.dburi
137 117
138 118 try:
139 119 curr_version = api.db_version(db_uri, repository_path)
140 120 msg = ('Found current database under version'
141 121 ' control with version %s' % curr_version)
142 122
143 123 except (RuntimeError, DatabaseNotControlledError), e:
144 124 curr_version = 1
145 125 msg = ('Current database is not under version control. Setting'
146 126 ' as version %s' % curr_version)
147 127 api.version_control(db_uri, repository_path, curr_version)
148 128
149 129 print (msg)
150 130
151 131 if curr_version == __dbversion__:
152 132 sys.exit('This database is already at the newest version')
153 133
154 134 #======================================================================
155 135 # UPGRADE STEPS
156 136 #======================================================================
157 137 class UpgradeSteps(object):
158 138 """Those steps follow schema versions so for example schema
159 139 for example schema with seq 002 == step_2 and so on.
160 140 """
161 141
162 142 def __init__(self, klass):
163 143 self.klass = klass
164 144
165 145 def step_0(self):
166 146 #step 0 is the schema upgrade, and than follow proper upgrades
167 147 print ('attempting to do database upgrade to version %s' \
168 148 % __dbversion__)
169 149 api.upgrade(db_uri, repository_path, __dbversion__)
170 150 print ('Schema upgrade completed')
171 151
172 152 def step_1(self):
173 153 pass
174 154
175 155 def step_2(self):
176 156 print ('Patching repo paths for newer version of RhodeCode')
177 157 self.klass.fix_repo_paths()
178 158
179 159 print ('Patching default user of RhodeCode')
180 160 self.klass.fix_default_user()
181 161
182 162 log.info('Changing ui settings')
183 163 self.klass.create_ui_settings()
184 164
185 165
186 166 upgrade_steps = [0] + range(curr_version + 1, __dbversion__ + 1)
187 167
188 168 #CALL THE PROPER ORDER OF STEPS TO PERFORM FULL UPGRADE
189 169 for step in upgrade_steps:
190 170 print ('performing upgrade step %s' % step)
191 171 callable = getattr(UpgradeSteps(self), 'step_%s' % step)()
192 172
193 173
194 174
195 175 def fix_repo_paths(self):
196 176 """Fixes a old rhodecode version path into new one without a '*'
197 177 """
198 178
199 179 paths = self.sa.query(RhodeCodeUi)\
200 180 .filter(RhodeCodeUi.ui_key == '/')\
201 181 .scalar()
202 182
203 183 paths.ui_value = paths.ui_value.replace('*', '')
204 184
205 185 try:
206 186 self.sa.add(paths)
207 187 self.sa.commit()
208 188 except:
209 189 self.sa.rollback()
210 190 raise
211 191
212 192 def fix_default_user(self):
213 193 """Fixes a old default user with some 'nicer' default values,
214 194 used mostly for anonymous access
215 195 """
216 196 def_user = self.sa.query(User)\
217 197 .filter(User.username == 'default')\
218 198 .one()
219 199
220 200 def_user.name = 'Anonymous'
221 201 def_user.lastname = 'User'
222 202 def_user.email = 'anonymous@rhodecode.org'
223 203
224 204 try:
225 205 self.sa.add(def_user)
226 206 self.sa.commit()
227 207 except:
228 208 self.sa.rollback()
229 209 raise
230 210
231 211
232 212
233 213 def admin_prompt(self, second=False):
234 214 if not self.tests:
235 215 import getpass
236 216
237 217
238 218 def get_password():
239 219 password = getpass.getpass('Specify admin password (min 6 chars):')
240 220 confirm = getpass.getpass('Confirm password:')
241 221
242 222 if password != confirm:
243 223 log.error('passwords mismatch')
244 224 return False
245 225 if len(password) < 6:
246 226 log.error('password is to short use at least 6 characters')
247 227 return False
248 228
249 229 return password
250 230
251 231 username = raw_input('Specify admin username:')
252 232
253 233 password = get_password()
254 234 if not password:
255 235 #second try
256 236 password = get_password()
257 237 if not password:
258 238 sys.exit()
259 239
260 240 email = raw_input('Specify admin email:')
261 241 self.create_user(username, password, email, True)
262 242 else:
263 243 log.info('creating admin and regular test users')
264 244 self.create_user('test_admin', 'test12', 'test_admin@mail.com', True)
265 245 self.create_user('test_regular', 'test12', 'test_regular@mail.com', False)
266 246 self.create_user('test_regular2', 'test12', 'test_regular2@mail.com', False)
267 247
268 248 def create_ui_settings(self):
269 249 """Creates ui settings, fills out hooks
270 250 and disables dotencode
271 251
272 252 """
273 253 #HOOKS
274 254 hooks1_key = 'changegroup.update'
275 255 hooks1_ = self.sa.query(RhodeCodeUi)\
276 256 .filter(RhodeCodeUi.ui_key == hooks1_key).scalar()
277 257
278 258 hooks1 = RhodeCodeUi() if hooks1_ is None else hooks1_
279 259 hooks1.ui_section = 'hooks'
280 260 hooks1.ui_key = hooks1_key
281 261 hooks1.ui_value = 'hg update >&2'
282 262 hooks1.ui_active = False
283 263
284 264 hooks2_key = 'changegroup.repo_size'
285 265 hooks2_ = self.sa.query(RhodeCodeUi)\
286 266 .filter(RhodeCodeUi.ui_key == hooks2_key).scalar()
287 267
288 268 hooks2 = RhodeCodeUi() if hooks2_ is None else hooks2_
289 269 hooks2.ui_section = 'hooks'
290 270 hooks2.ui_key = hooks2_key
291 271 hooks2.ui_value = 'python:rhodecode.lib.hooks.repo_size'
292 272
293 273 hooks3 = RhodeCodeUi()
294 274 hooks3.ui_section = 'hooks'
295 275 hooks3.ui_key = 'pretxnchangegroup.push_logger'
296 276 hooks3.ui_value = 'python:rhodecode.lib.hooks.log_push_action'
297 277
298 278 hooks4 = RhodeCodeUi()
299 279 hooks4.ui_section = 'hooks'
300 280 hooks4.ui_key = 'preoutgoing.pull_logger'
301 281 hooks4.ui_value = 'python:rhodecode.lib.hooks.log_pull_action'
302 282
303 283 #For mercurial 1.7 set backward comapatibility with format
304 284 dotencode_disable = RhodeCodeUi()
305 285 dotencode_disable.ui_section = 'format'
306 286 dotencode_disable.ui_key = 'dotencode'
307 287 dotencode_disable.ui_value = 'false'
308 288
309 289 try:
310 290 self.sa.add(hooks1)
311 291 self.sa.add(hooks2)
312 292 self.sa.add(hooks3)
313 293 self.sa.add(hooks4)
314 294 self.sa.add(dotencode_disable)
315 295 self.sa.commit()
316 296 except:
317 297 self.sa.rollback()
318 298 raise
319 299
320 300
321 301 def create_ldap_options(self):
322 302 """Creates ldap settings"""
323 303
324 304 try:
325 for k in ['ldap_active', 'ldap_host', 'ldap_port', 'ldap_ldaps',
326 'ldap_dn_user', 'ldap_dn_pass', 'ldap_base_dn']:
305 for k, v in [('ldap_active', 'false'),
306 ('ldap_host', ''),
307 ('ldap_port', '389'),
308 ('ldap_ldaps', 'false'),
309 ('ldap_dn_user', ''), ('ldap_dn_pass', ''),
310 ('ldap_base_dn', '')]:
327 311
328 setting = RhodeCodeSettings(k, '')
312 setting = RhodeCodeSettings(k, v)
329 313 self.sa.add(setting)
330 314 self.sa.commit()
331 315 except:
332 316 self.sa.rollback()
333 317 raise
334 318
335 319 def config_prompt(self, test_repo_path=''):
336 320 log.info('Setting up repositories config')
337 321
338 322 if not self.tests and not test_repo_path:
339 323 path = raw_input('Specify valid full path to your repositories'
340 324 ' you can change this later in application settings:')
341 325 else:
342 326 path = test_repo_path
343 327
344 328 if not os.path.isdir(path):
345 329 log.error('You entered wrong path: %s', path)
346 330 sys.exit()
347 331
348 332 self.create_ui_settings()
349 333
350 334 #HG UI OPTIONS
351 335 web1 = RhodeCodeUi()
352 336 web1.ui_section = 'web'
353 337 web1.ui_key = 'push_ssl'
354 338 web1.ui_value = 'false'
355 339
356 340 web2 = RhodeCodeUi()
357 341 web2.ui_section = 'web'
358 342 web2.ui_key = 'allow_archive'
359 343 web2.ui_value = 'gz zip bz2'
360 344
361 345 web3 = RhodeCodeUi()
362 346 web3.ui_section = 'web'
363 347 web3.ui_key = 'allow_push'
364 348 web3.ui_value = '*'
365 349
366 350 web4 = RhodeCodeUi()
367 351 web4.ui_section = 'web'
368 352 web4.ui_key = 'baseurl'
369 353 web4.ui_value = '/'
370 354
371 355 paths = RhodeCodeUi()
372 356 paths.ui_section = 'paths'
373 357 paths.ui_key = '/'
374 358 paths.ui_value = path
375 359
376 360
377 361 hgsettings1 = RhodeCodeSettings('realm', 'RhodeCode authentication')
378 362 hgsettings2 = RhodeCodeSettings('title', 'RhodeCode')
379 363
380 364
381 365 try:
382 366 self.sa.add(web1)
383 367 self.sa.add(web2)
384 368 self.sa.add(web3)
385 369 self.sa.add(web4)
386 370 self.sa.add(paths)
387 371 self.sa.add(hgsettings1)
388 372 self.sa.add(hgsettings2)
389 373
390 374 self.sa.commit()
391 375 except:
392 376 self.sa.rollback()
393 377 raise
394 378
395 379 self.create_ldap_options()
396 380
397 381 log.info('created ui config')
398 382
399 383 def create_user(self, username, password, email='', admin=False):
400 384 log.info('creating administrator user %s', username)
401 385 new_user = User()
402 386 new_user.username = username
403 387 new_user.password = get_crypt_password(password)
404 388 new_user.name = 'RhodeCode'
405 389 new_user.lastname = 'Admin'
406 390 new_user.email = email
407 391 new_user.admin = admin
408 392 new_user.active = True
409 393
410 394 try:
411 395 self.sa.add(new_user)
412 396 self.sa.commit()
413 397 except:
414 398 self.sa.rollback()
415 399 raise
416 400
417 401 def create_default_user(self):
418 402 log.info('creating default user')
419 403 #create default user for handling default permissions.
420 404 def_user = User()
421 405 def_user.username = 'default'
422 406 def_user.password = get_crypt_password(str(uuid.uuid1())[:8])
423 407 def_user.name = 'Anonymous'
424 408 def_user.lastname = 'User'
425 409 def_user.email = 'anonymous@rhodecode.org'
426 410 def_user.admin = False
427 411 def_user.active = False
428 412 try:
429 413 self.sa.add(def_user)
430 414 self.sa.commit()
431 415 except:
432 416 self.sa.rollback()
433 417 raise
434 418
435 419 def create_permissions(self):
436 420 #module.(access|create|change|delete)_[name]
437 421 #module.(read|write|owner)
438 422 perms = [('repository.none', 'Repository no access'),
439 423 ('repository.read', 'Repository read access'),
440 424 ('repository.write', 'Repository write access'),
441 425 ('repository.admin', 'Repository admin access'),
442 426 ('hg.admin', 'Hg Administrator'),
443 427 ('hg.create.repository', 'Repository create'),
444 428 ('hg.create.none', 'Repository creation disabled'),
445 429 ('hg.register.none', 'Register disabled'),
446 430 ('hg.register.manual_activate', 'Register new user with rhodecode without manual activation'),
447 431 ('hg.register.auto_activate', 'Register new user with rhodecode without auto activation'),
448 432 ]
449 433
450 434 for p in perms:
451 435 new_perm = Permission()
452 436 new_perm.permission_name = p[0]
453 437 new_perm.permission_longname = p[1]
454 438 try:
455 439 self.sa.add(new_perm)
456 440 self.sa.commit()
457 441 except:
458 442 self.sa.rollback()
459 443 raise
460 444
461 445 def populate_default_permissions(self):
462 446 log.info('creating default user permissions')
463 447
464 448 default_user = self.sa.query(User)\
465 449 .filter(User.username == 'default').scalar()
466 450
467 451 reg_perm = UserToPerm()
468 452 reg_perm.user = default_user
469 453 reg_perm.permission = self.sa.query(Permission)\
470 454 .filter(Permission.permission_name == 'hg.register.manual_activate')\
471 455 .scalar()
472 456
473 457 create_repo_perm = UserToPerm()
474 458 create_repo_perm.user = default_user
475 459 create_repo_perm.permission = self.sa.query(Permission)\
476 460 .filter(Permission.permission_name == 'hg.create.repository')\
477 461 .scalar()
478 462
479 463 default_repo_perm = UserToPerm()
480 464 default_repo_perm.user = default_user
481 465 default_repo_perm.permission = self.sa.query(Permission)\
482 466 .filter(Permission.permission_name == 'repository.read')\
483 467 .scalar()
484 468
485 469 try:
486 470 self.sa.add(reg_perm)
487 471 self.sa.add(create_repo_perm)
488 472 self.sa.add(default_repo_perm)
489 473 self.sa.commit()
490 474 except:
491 475 self.sa.rollback()
492 476 raise
493 477
@@ -1,555 +1,557 b''
1 1 """Helper functions
2 2
3 3 Consists of functions to typically be used within templates, but also
4 4 available to Controllers. This module is available to both as 'h'.
5 5 """
6 6 import random
7 7 import hashlib
8 8 from pygments.formatters import HtmlFormatter
9 9 from pygments import highlight as code_highlight
10 10 from pylons import url, app_globals as g
11 11 from pylons.i18n.translation import _, ungettext
12 12 from vcs.utils.annotate import annotate_highlight
13 13 from webhelpers.html import literal, HTML, escape
14 14 from webhelpers.html.tools import *
15 15 from webhelpers.html.builder import make_tag
16 16 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
17 17 end_form, file, form, hidden, image, javascript_link, link_to, link_to_if, \
18 18 link_to_unless, ol, required_legend, select, stylesheet_link, submit, text, \
19 19 password, textarea, title, ul, xml_declaration, radio
20 20 from webhelpers.html.tools import auto_link, button_to, highlight, js_obfuscate, \
21 21 mail_to, strip_links, strip_tags, tag_re
22 22 from webhelpers.number import format_byte_size, format_bit_size
23 23 from webhelpers.pylonslib import Flash as _Flash
24 24 from webhelpers.pylonslib.secure_form import secure_form
25 25 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
26 26 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
27 27 replace_whitespace, urlify, truncate, wrap_paragraphs
28 28 from webhelpers.date import time_ago_in_words
29 29
30 30 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
31 31 convert_boolean_attrs, NotGiven
32 32
33 33 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
34 34 _set_input_attrs(attrs, type, name, value)
35 35 _set_id_attr(attrs, id, name)
36 36 convert_boolean_attrs(attrs, ["disabled"])
37 37 return HTML.input(**attrs)
38 38
39 39 reset = _reset
40 40
41 41
42 42 def get_token():
43 43 """Return the current authentication token, creating one if one doesn't
44 44 already exist.
45 45 """
46 46 token_key = "_authentication_token"
47 47 from pylons import session
48 48 if not token_key in session:
49 49 try:
50 50 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
51 51 except AttributeError: # Python < 2.4
52 52 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
53 53 session[token_key] = token
54 54 if hasattr(session, 'save'):
55 55 session.save()
56 56 return session[token_key]
57 57
58 58
59 59 #Custom helpers here :)
60 60 class _Link(object):
61 61 '''
62 62 Make a url based on label and url with help of url_for
63 63 :param label:name of link if not defined url is used
64 64 :param url: the url for link
65 65 '''
66 66
67 67 def __call__(self, label='', *url_, **urlargs):
68 68 if label is None or '':
69 69 label = url
70 70 link_fn = link_to(label, url(*url_, **urlargs))
71 71 return link_fn
72 72
73 73 link = _Link()
74 74
75 75 class _GetError(object):
76 76
77 77 def __call__(self, field_name, form_errors):
78 78 tmpl = """<span class="error_msg">%s</span>"""
79 79 if form_errors and form_errors.has_key(field_name):
80 80 return literal(tmpl % form_errors.get(field_name))
81 81
82 82 get_error = _GetError()
83 83
84 84 def recursive_replace(str, replace=' '):
85 85 """
86 86 Recursive replace of given sign to just one instance
87 87 :param str: given string
88 88 :param replace:char to find and replace multiple instances
89 89
90 90 Examples::
91 91 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
92 92 'Mighty-Mighty-Bo-sstones'
93 93 """
94 94
95 95 if str.find(replace * 2) == -1:
96 96 return str
97 97 else:
98 98 str = str.replace(replace * 2, replace)
99 99 return recursive_replace(str, replace)
100 100
101 101 class _ToolTip(object):
102 102
103 103 def __call__(self, tooltip_title, trim_at=50):
104 104 """
105 105 Special function just to wrap our text into nice formatted autowrapped
106 106 text
107 107 :param tooltip_title:
108 108 """
109 109
110 110 return wrap_paragraphs(escape(tooltip_title), trim_at)\
111 111 .replace('\n', '<br/>')
112 112
113 113 def activate(self):
114 114 """
115 115 Adds tooltip mechanism to the given Html all tooltips have to have
116 116 set class tooltip and set attribute tooltip_title.
117 117 Then a tooltip will be generated based on that
118 118 All with yui js tooltip
119 119 """
120 120
121 121 js = '''
122 122 YAHOO.util.Event.onDOMReady(function(){
123 123 function toolTipsId(){
124 124 var ids = [];
125 125 var tts = YAHOO.util.Dom.getElementsByClassName('tooltip');
126 126
127 127 for (var i = 0; i < tts.length; i++) {
128 128 //if element doesn't not have and id autogenerate one for tooltip
129 129
130 130 if (!tts[i].id){
131 131 tts[i].id='tt'+i*100;
132 132 }
133 133 ids.push(tts[i].id);
134 134 }
135 135 return ids
136 136 };
137 137 var myToolTips = new YAHOO.widget.Tooltip("tooltip", {
138 138 context: toolTipsId(),
139 139 monitorresize:false,
140 140 xyoffset :[0,0],
141 141 autodismissdelay:300000,
142 142 hidedelay:5,
143 143 showdelay:20,
144 144 });
145 145
146 146 //Mouse Over event disabled for new repositories since they don't
147 147 //have last commit message
148 148 myToolTips.contextMouseOverEvent.subscribe(
149 149 function(type, args) {
150 150 var context = args[0];
151 151 var txt = context.getAttribute('tooltip_title');
152 152 if(txt){
153 153 return true;
154 154 }
155 155 else{
156 156 return false;
157 157 }
158 158 });
159 159
160 160
161 161 // Set the text for the tooltip just before we display it. Lazy method
162 162 myToolTips.contextTriggerEvent.subscribe(
163 163 function(type, args) {
164 164
165 165
166 166 var context = args[0];
167 167
168 168 var txt = context.getAttribute('tooltip_title');
169 169 this.cfg.setProperty("text", txt);
170 170
171 171
172 172 // positioning of tooltip
173 173 var tt_w = this.element.clientWidth;
174 174 var tt_h = this.element.clientHeight;
175 175
176 176 var context_w = context.offsetWidth;
177 177 var context_h = context.offsetHeight;
178 178
179 179 var pos_x = YAHOO.util.Dom.getX(context);
180 180 var pos_y = YAHOO.util.Dom.getY(context);
181 181
182 182 var display_strategy = 'top';
183 183 var xy_pos = [0,0];
184 184 switch (display_strategy){
185 185
186 186 case 'top':
187 187 var cur_x = (pos_x+context_w/2)-(tt_w/2);
188 188 var cur_y = (pos_y-tt_h-4);
189 189 xy_pos = [cur_x,cur_y];
190 190 break;
191 191 case 'bottom':
192 192 var cur_x = (pos_x+context_w/2)-(tt_w/2);
193 193 var cur_y = pos_y+context_h+4;
194 194 xy_pos = [cur_x,cur_y];
195 195 break;
196 196 case 'left':
197 197 var cur_x = (pos_x-tt_w-4);
198 198 var cur_y = pos_y-((tt_h/2)-context_h/2);
199 199 xy_pos = [cur_x,cur_y];
200 200 break;
201 201 case 'right':
202 202 var cur_x = (pos_x+context_w+4);
203 203 var cur_y = pos_y-((tt_h/2)-context_h/2);
204 204 xy_pos = [cur_x,cur_y];
205 205 break;
206 206 default:
207 207 var cur_x = (pos_x+context_w/2)-(tt_w/2);
208 208 var cur_y = pos_y-tt_h-4;
209 209 xy_pos = [cur_x,cur_y];
210 210 break;
211 211
212 212 }
213 213
214 214 this.cfg.setProperty("xy",xy_pos);
215 215
216 216 });
217 217
218 218 //Mouse out
219 219 myToolTips.contextMouseOutEvent.subscribe(
220 220 function(type, args) {
221 221 var context = args[0];
222 222
223 223 });
224 224 });
225 225 '''
226 226 return literal(js)
227 227
228 228 tooltip = _ToolTip()
229 229
230 230 class _FilesBreadCrumbs(object):
231 231
232 232 def __call__(self, repo_name, rev, paths):
233 if isinstance(paths, str):
234 paths = paths.decode('utf-8', 'replace')
233 235 url_l = [link_to(repo_name, url('files_home',
234 236 repo_name=repo_name,
235 237 revision=rev, f_path=''))]
236 238 paths_l = paths.split('/')
237 239
238 240 for cnt, p in enumerate(paths_l):
239 241 if p != '':
240 242 url_l.append(link_to(p, url('files_home',
241 243 repo_name=repo_name,
242 244 revision=rev,
243 245 f_path='/'.join(paths_l[:cnt + 1]))))
244 246
245 247 return literal('/'.join(url_l))
246 248
247 249 files_breadcrumbs = _FilesBreadCrumbs()
248 250 class CodeHtmlFormatter(HtmlFormatter):
249 251
250 252 def wrap(self, source, outfile):
251 253 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
252 254
253 255 def _wrap_code(self, source):
254 256 for cnt, it in enumerate(source):
255 257 i, t = it
256 258 t = '<div id="#S-%s">%s</div>' % (cnt + 1, t)
257 259 yield i, t
258 260 def pygmentize(filenode, **kwargs):
259 261 """
260 262 pygmentize function using pygments
261 263 :param filenode:
262 264 """
263 265 return literal(code_highlight(filenode.content,
264 266 filenode.lexer, CodeHtmlFormatter(**kwargs)))
265 267
266 268 def pygmentize_annotation(filenode, **kwargs):
267 269 """
268 270 pygmentize function for annotation
269 271 :param filenode:
270 272 """
271 273
272 274 color_dict = {}
273 275 def gen_color():
274 276 """generator for getting 10k of evenly distibuted colors using hsv color
275 277 and golden ratio.
276 278 """
277 279 import colorsys
278 280 n = 10000
279 281 golden_ratio = 0.618033988749895
280 282 h = 0.22717784590367374
281 283 #generate 10k nice web friendly colors in the same order
282 284 for c in xrange(n):
283 285 h += golden_ratio
284 286 h %= 1
285 287 HSV_tuple = [h, 0.95, 0.95]
286 288 RGB_tuple = colorsys.hsv_to_rgb(*HSV_tuple)
287 289 yield map(lambda x:str(int(x * 256)), RGB_tuple)
288 290
289 291 cgenerator = gen_color()
290 292
291 293 def get_color_string(cs):
292 294 if color_dict.has_key(cs):
293 295 col = color_dict[cs]
294 296 else:
295 297 col = color_dict[cs] = cgenerator.next()
296 298 return "color: rgb(%s)! important;" % (', '.join(col))
297 299
298 300 def url_func(changeset):
299 301 tooltip_html = "<div style='font-size:0.8em'><b>Author:</b>" + \
300 302 " %s<br/><b>Date:</b> %s</b><br/><b>Message:</b> %s<br/></div>"
301 303
302 304 tooltip_html = tooltip_html % (changeset.author,
303 305 changeset.date,
304 306 tooltip(changeset.message))
305 307 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
306 308 short_id(changeset.raw_id))
307 309 uri = link_to(
308 310 lnk_format,
309 311 url('changeset_home', repo_name=changeset.repository.name,
310 312 revision=changeset.raw_id),
311 313 style=get_color_string(changeset.raw_id),
312 314 class_='tooltip',
313 315 tooltip_title=tooltip_html
314 316 )
315 317
316 318 uri += '\n'
317 319 return uri
318 320 return literal(annotate_highlight(filenode, url_func, **kwargs))
319 321
320 322 def repo_name_slug(value):
321 323 """Return slug of name of repository
322 324 This function is called on each creation/modification
323 325 of repository to prevent bad names in repo
324 326 """
325 327 slug = remove_formatting(value)
326 328 slug = strip_tags(slug)
327 329
328 330 for c in """=[]\;'"<>,/~!@#$%^&*()+{}|: """:
329 331 slug = slug.replace(c, '-')
330 332 slug = recursive_replace(slug, '-')
331 333 slug = collapse(slug, '-')
332 334 return slug
333 335
334 336 def get_changeset_safe(repo, rev):
335 337 from vcs.backends.base import BaseRepository
336 338 from vcs.exceptions import RepositoryError
337 339 if not isinstance(repo, BaseRepository):
338 340 raise Exception('You must pass an Repository '
339 341 'object as first argument got %s', type(repo))
340 342
341 343 try:
342 344 cs = repo.get_changeset(rev)
343 345 except RepositoryError:
344 346 from rhodecode.lib.utils import EmptyChangeset
345 347 cs = EmptyChangeset()
346 348 return cs
347 349
348 350
349 351 flash = _Flash()
350 352
351 353
352 354 #==============================================================================
353 355 # MERCURIAL FILTERS available via h.
354 356 #==============================================================================
355 357 from mercurial import util
356 358 from mercurial.templatefilters import person as _person
357 359
358 360
359 361
360 362 def _age(curdate):
361 363 """turns a datetime into an age string."""
362 364
363 365 if not curdate:
364 366 return ''
365 367
366 368 from datetime import timedelta, datetime
367 369
368 370 agescales = [("year", 3600 * 24 * 365),
369 371 ("month", 3600 * 24 * 30),
370 372 ("day", 3600 * 24),
371 373 ("hour", 3600),
372 374 ("minute", 60),
373 375 ("second", 1), ]
374 376
375 377 age = datetime.now() - curdate
376 378 age_seconds = (age.days * agescales[2][1]) + age.seconds
377 379 pos = 1
378 380 for scale in agescales:
379 381 if scale[1] <= age_seconds:
380 382 if pos == 6:pos = 5
381 383 return time_ago_in_words(curdate, agescales[pos][0]) + ' ' + _('ago')
382 384 pos += 1
383 385
384 386 return _('just now')
385 387
386 388 age = lambda x:_age(x)
387 389 capitalize = lambda x: x.capitalize()
388 390 email = util.email
389 391 email_or_none = lambda x: util.email(x) if util.email(x) != x else None
390 392 person = lambda x: _person(x)
391 393 short_id = lambda x: x[:12]
392 394
393 395
394 396 def bool2icon(value):
395 397 """
396 398 Returns True/False values represented as small html image of true/false
397 399 icons
398 400 :param value: bool value
399 401 """
400 402
401 403 if value is True:
402 404 return HTML.tag('img', src=url("/images/icons/accept.png"), alt=_('True'))
403 405
404 406 if value is False:
405 407 return HTML.tag('img', src=url("/images/icons/cancel.png"), alt=_('False'))
406 408
407 409 return value
408 410
409 411
410 412 def action_parser(user_log):
411 413 """
412 414 This helper will map the specified string action into translated
413 415 fancy names with icons and links
414 416
415 417 @param action:
416 418 """
417 419 action = user_log.action
418 420 action_params = ' '
419 421
420 422 x = action.split(':')
421 423
422 424 if len(x) > 1:
423 425 action, action_params = x
424 426
425 427 def get_cs_links():
426 428 revs_limit = 5
427 429 revs = action_params.split(',')
428 430 cs_links = " " + ', '.join ([link(rev,
429 431 url('changeset_home',
430 432 repo_name=user_log.repository.repo_name,
431 433 revision=rev)) for rev in revs[:revs_limit] ])
432 434 if len(revs) > revs_limit:
433 435 uniq_id = revs[0]
434 436 html_tmpl = ('<span> %s '
435 437 '<a class="show_more" id="_%s" href="#">%s</a> '
436 438 '%s</span>')
437 439 cs_links += html_tmpl % (_('and'), uniq_id, _('%s more') \
438 440 % (len(revs) - revs_limit),
439 441 _('revisions'))
440 442
441 443 html_tmpl = '<span id="%s" style="display:none"> %s </span>'
442 444 cs_links += html_tmpl % (uniq_id, ', '.join([link(rev,
443 445 url('changeset_home',
444 446 repo_name=user_log.repository.repo_name,
445 447 revision=rev)) for rev in revs[revs_limit:] ]))
446 448
447 449 return cs_links
448 450
449 451 def get_fork_name():
450 452 repo_name = action_params
451 453 return str(link_to(action_params, url('summary_home',
452 454 repo_name=repo_name,)))
453 455
454 456 map = {'user_deleted_repo':(_('[deleted] repository'), None),
455 457 'user_created_repo':(_('[created] repository'), None),
456 458 'user_forked_repo':(_('[forked] repository as'), get_fork_name),
457 459 'user_updated_repo':(_('[updated] repository'), None),
458 460 'admin_deleted_repo':(_('[delete] repository'), None),
459 461 'admin_created_repo':(_('[created] repository'), None),
460 462 'admin_forked_repo':(_('[forked] repository'), None),
461 463 'admin_updated_repo':(_('[updated] repository'), None),
462 464 'push':(_('[pushed] '), get_cs_links),
463 465 'pull':(_('[pulled] '), None),
464 466 'started_following_repo':(_('[started following] repository'), None),
465 467 'stopped_following_repo':(_('[stopped following] repository'), None),
466 468 }
467 469
468 470 action_str = map.get(action, action)
469 471 action = action_str[0].replace('[', '<span class="journal_highlight">')\
470 472 .replace(']', '</span>')
471 473 action_params_func = lambda :""
472 474
473 475 if action_str[1] is not None:
474 476 action_params_func = action_str[1]
475 477
476 478 return literal(action + " " + action_params_func())
477 479
478 480 def action_parser_icon(user_log):
479 481 action = user_log.action
480 482 action_params = None
481 483 x = action.split(':')
482 484
483 485 if len(x) > 1:
484 486 action, action_params = x
485 487
486 tmpl = """<img src="%s/%s" alt="%s"/>"""
488 tmpl = """<img src="%s%s" alt="%s"/>"""
487 489 map = {'user_deleted_repo':'database_delete.png',
488 490 'user_created_repo':'database_add.png',
489 491 'user_forked_repo':'arrow_divide.png',
490 492 'user_updated_repo':'database_edit.png',
491 493 'admin_deleted_repo':'database_delete.png',
492 494 'admin_created_repo':'database_add.png',
493 495 'admin_forked_repo':'arrow_divide.png',
494 496 'admin_updated_repo':'database_edit.png',
495 497 'push':'script_add.png',
496 498 'pull':'down_16.png',
497 499 'started_following_repo':'heart_add.png',
498 500 'stopped_following_repo':'heart_delete.png',
499 501 }
500 502 return literal(tmpl % ((url('/images/icons/')),
501 503 map.get(action, action), action))
502 504
503 505
504 506 #==============================================================================
505 507 # PERMS
506 508 #==============================================================================
507 509 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
508 510 HasRepoPermissionAny, HasRepoPermissionAll
509 511
510 512 #==============================================================================
511 513 # GRAVATAR URL
512 514 #==============================================================================
513 515 import hashlib
514 516 import urllib
515 517 from pylons import request
516 518
517 519 def gravatar_url(email_address, size=30):
518 520 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
519 521 default = 'identicon'
520 522 baseurl_nossl = "http://www.gravatar.com/avatar/"
521 523 baseurl_ssl = "https://secure.gravatar.com/avatar/"
522 524 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
523 525
524 526
525 527 # construct the url
526 528 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
527 529 gravatar_url += urllib.urlencode({'d':default, 's':str(size)})
528 530
529 531 return gravatar_url
530 532
531 533 def safe_unicode(str):
532 534 """safe unicode function. In case of UnicodeDecode error we try to return
533 535 unicode with errors replace, if this failes we return unicode with
534 536 string_escape decoding """
535 537
536 538 try:
537 539 u_str = unicode(str)
538 540 except UnicodeDecodeError:
539 541 try:
540 542 u_str = unicode(str, 'utf-8', 'replace')
541 543 except UnicodeDecodeError:
542 544 #incase we have a decode error just represent as byte string
543 545 u_str = unicode(str(str).encode('string_escape'))
544 546
545 547 return u_str
546 548
547 549 def changed_tooltip(nodes):
548 550 if nodes:
549 551 pref = ': <br/> '
550 552 suf = ''
551 553 if len(nodes) > 30:
552 554 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
553 return literal(pref + '<br/> '.join([x.path for x in nodes[:30]]) + suf)
555 return literal(pref + '<br/> '.join([x.path.decode('utf-8', 'replace') for x in nodes[:30]]) + suf)
554 556 else:
555 557 return ': ' + _('No Files')
@@ -1,271 +1,249 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.db
4 4 ~~~~~~~~~~~~~~~~~~
5 5
6 6 Database Models for RhodeCode
7 7
8 8 :created_on: Apr 08, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software; you can redistribute it and/or
14 14 # modify it under the terms of the GNU General Public License
15 15 # as published by the Free Software Foundation; version 2
16 16 # of the License or (at your opinion) any later version of the license.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program; if not, write to the Free Software
25 25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 26 # MA 02110-1301, USA.
27 27 import logging
28 28 import datetime
29 29 from datetime import date
30 30
31 31 from sqlalchemy import *
32 32 from sqlalchemy.exc import DatabaseError
33 from sqlalchemy.orm import relationship, backref, class_mapper
34 from sqlalchemy.orm.session import Session
33 from sqlalchemy.orm import relationship, backref
34 from sqlalchemy.orm.interfaces import MapperExtension
35 35
36 from rhodecode.model.meta import Base
36 from rhodecode.model.meta import Base, Session
37 37
38 38 log = logging.getLogger(__name__)
39 39
40 class BaseModel(object):
41 40
42 @classmethod
43 def _get_keys(cls):
44 """return column names for this model """
45 return class_mapper(cls).c.keys()
46
47 def get_dict(self):
48 """return dict with keys and values corresponding
49 to this model data """
50
51 d = {}
52 for k in self._get_keys():
53 d[k] = getattr(self, k)
54 return d
55
56 def get_appstruct(self):
57 """return list with keys and values tupples corresponding
58 to this model data """
59
60 l = []
61 for k in self._get_keys():
62 l.append((k, getattr(self, k),))
63 return l
64
65 def populate_obj(self, populate_dict):
66 """populate model with data from given populate_dict"""
67
68 for k in self._get_keys():
69 if k in populate_dict:
70 setattr(self, k, populate_dict[k])
71
72 class RhodeCodeSettings(Base, BaseModel):
41 class RhodeCodeSettings(Base):
73 42 __tablename__ = 'rhodecode_settings'
74 43 __table_args__ = (UniqueConstraint('app_settings_name'), {'useexisting':True})
75 44 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
76 app_settings_name = Column("app_settings_name", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
77 app_settings_value = Column("app_settings_value", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
45 app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
46 app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
78 47
79 48 def __init__(self, k='', v=''):
80 49 self.app_settings_name = k
81 50 self.app_settings_value = v
82 51
83 52 def __repr__(self):
84 53 return "<%s('%s:%s')>" % (self.__class__.__name__,
85 54 self.app_settings_name, self.app_settings_value)
86 55
87 class RhodeCodeUi(Base, BaseModel):
56 class RhodeCodeUi(Base):
88 57 __tablename__ = 'rhodecode_ui'
89 58 __table_args__ = {'useexisting':True}
90 59 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
91 ui_section = Column("ui_section", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
92 ui_key = Column("ui_key", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
93 ui_value = Column("ui_value", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
60 ui_section = Column("ui_section", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
61 ui_key = Column("ui_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
62 ui_value = Column("ui_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
94 63 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
95 64
96 65
97 class User(Base, BaseModel):
66 class User(Base):
98 67 __tablename__ = 'users'
99 68 __table_args__ = (UniqueConstraint('username'), UniqueConstraint('email'), {'useexisting':True})
100 69 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
101 username = Column("username", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
102 password = Column("password", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
70 username = Column("username", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
71 password = Column("password", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
103 72 active = Column("active", Boolean(), nullable=True, unique=None, default=None)
104 73 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
105 name = Column("name", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
106 lastname = Column("lastname", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
107 email = Column("email", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
74 name = Column("name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
75 lastname = Column("lastname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
76 email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
108 77 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
109 78 is_ldap = Column("is_ldap", Boolean(), nullable=False, unique=None, default=False)
110 79
111 80 user_log = relationship('UserLog', cascade='all')
112 81 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
113 82
114 83 repositories = relationship('Repository')
115 84 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
116 85
117 86 @property
118 87 def full_contact(self):
119 88 return '%s %s <%s>' % (self.name, self.lastname, self.email)
120 89
90 @property
91 def short_contact(self):
92 return '%s %s' % (self.name, self.lastname)
93
121 94
122 95 @property
123 96 def is_admin(self):
124 97 return self.admin
125 98
126 99 def __repr__(self):
127 100 return "<%s('id:%s:%s')>" % (self.__class__.__name__,
128 101 self.user_id, self.username)
129 102
103 @classmethod
104 def by_username(cls, username):
105 return Session.query(cls).filter(cls.username == username).one()
106
107
130 108 def update_lastlogin(self):
131 109 """Update user lastlogin"""
132 110
133 111 try:
134 112 session = Session.object_session(self)
135 113 self.last_login = datetime.datetime.now()
136 114 session.add(self)
137 115 session.commit()
138 116 log.debug('updated user %s lastlogin', self.username)
139 117 except (DatabaseError,):
140 118 session.rollback()
141 119
142 120
143 class UserLog(Base, BaseModel):
121 class UserLog(Base):
144 122 __tablename__ = 'user_logs'
145 123 __table_args__ = {'useexisting':True}
146 124 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
147 125 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
148 126 repository_id = Column("repository_id", Integer(length=None, convert_unicode=False, assert_unicode=None), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
149 repository_name = Column("repository_name", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
150 user_ip = Column("user_ip", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
151 action = Column("action", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
127 repository_name = Column("repository_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
128 user_ip = Column("user_ip", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
129 action = Column("action", UnicodeText(length=1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
152 130 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
153 131
154 132 @property
155 133 def action_as_day(self):
156 134 return date(*self.action_date.timetuple()[:3])
157 135
158 136 user = relationship('User')
159 137 repository = relationship('Repository')
160 138
161 class Repository(Base, BaseModel):
139 class Repository(Base):
162 140 __tablename__ = 'repositories'
163 141 __table_args__ = (UniqueConstraint('repo_name'), {'useexisting':True},)
164 142 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
165 repo_name = Column("repo_name", String(length=None, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
166 repo_type = Column("repo_type", String(length=None, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default='hg')
143 repo_name = Column("repo_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
144 repo_type = Column("repo_type", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default='hg')
167 145 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
168 146 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
169 147 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
170 description = Column("description", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
148 description = Column("description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
171 149 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
172 150
173 151 user = relationship('User')
174 152 fork = relationship('Repository', remote_side=repo_id)
175 153 repo_to_perm = relationship('RepoToPerm', cascade='all')
176 154 stats = relationship('Statistics', cascade='all', uselist=False)
177 155
178 156 repo_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
179 157
180 158 logs = relationship('UserLog', cascade='all')
181
159
182 160 def __repr__(self):
183 161 return "<%s('%s:%s')>" % (self.__class__.__name__,
184 162 self.repo_id, self.repo_name)
185 163
186 class Permission(Base, BaseModel):
164 class Permission(Base):
187 165 __tablename__ = 'permissions'
188 166 __table_args__ = {'useexisting':True}
189 167 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
190 permission_name = Column("permission_name", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
191 permission_longname = Column("permission_longname", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
168 permission_name = Column("permission_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
169 permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
192 170
193 171 def __repr__(self):
194 172 return "<%s('%s:%s')>" % (self.__class__.__name__,
195 173 self.permission_id, self.permission_name)
196 174
197 class RepoToPerm(Base, BaseModel):
175 class RepoToPerm(Base):
198 176 __tablename__ = 'repo_to_perm'
199 177 __table_args__ = (UniqueConstraint('user_id', 'repository_id'), {'useexisting':True})
200 178 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
201 179 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
202 180 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
203 181 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
204 182
205 183 user = relationship('User')
206 184 permission = relationship('Permission')
207 185 repository = relationship('Repository')
208 186
209 class UserToPerm(Base, BaseModel):
187 class UserToPerm(Base):
210 188 __tablename__ = 'user_to_perm'
211 189 __table_args__ = (UniqueConstraint('user_id', 'permission_id'), {'useexisting':True})
212 190 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
213 191 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
214 192 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
215 193
216 194 user = relationship('User')
217 195 permission = relationship('Permission')
218 196
219 class Statistics(Base, BaseModel):
197 class Statistics(Base):
220 198 __tablename__ = 'statistics'
221 199 __table_args__ = (UniqueConstraint('repository_id'), {'useexisting':True})
222 200 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
223 repository_id = Column("repository_id", Integer(), ForeignKey(u'repositories.repo_id'), nullable=False, unique=True, default=None)
201 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
224 202 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
225 commit_activity = Column("commit_activity", LargeBinary(), nullable=False)#JSON data
203 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
226 204 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
227 languages = Column("languages", LargeBinary(), nullable=False)#JSON data
205 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
228 206
229 207 repository = relationship('Repository', single_parent=True)
230 208
231 class UserFollowing(Base, BaseModel):
209 class UserFollowing(Base):
232 210 __tablename__ = 'user_followings'
233 211 __table_args__ = (UniqueConstraint('user_id', 'follows_repository_id'),
234 212 UniqueConstraint('user_id', 'follows_user_id')
235 213 , {'useexisting':True})
236 214
237 215 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
238 216 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
239 217 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
240 218 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
241 219
242 220 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
243 221
244 222 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
245 223 follows_repository = relationship('Repository', order_by='Repository.repo_name')
246 224
247 class CacheInvalidation(Base, BaseModel):
225 class CacheInvalidation(Base):
248 226 __tablename__ = 'cache_invalidation'
249 227 __table_args__ = (UniqueConstraint('cache_key'), {'useexisting':True})
250 228 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
251 cache_key = Column("cache_key", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
252 cache_args = Column("cache_args", String(length=None, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
229 cache_key = Column("cache_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
230 cache_args = Column("cache_args", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
253 231 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
254 232
255 233
256 234 def __init__(self, cache_key, cache_args=''):
257 235 self.cache_key = cache_key
258 236 self.cache_args = cache_args
259 237 self.cache_active = False
260 238
261 239 def __repr__(self):
262 240 return "<%s('%s:%s')>" % (self.__class__.__name__,
263 241 self.cache_id, self.cache_key)
264 242
265 class DbMigrateVersion(Base, BaseModel):
243 class DbMigrateVersion(Base):
266 244 __tablename__ = 'db_migrate_version'
267 245 __table_args__ = {'useexisting':True}
268 repository_id = Column('repository_id', String(250), primary_key=True)
246 repository_id = Column('repository_id', String(255), primary_key=True)
269 247 repository_path = Column('repository_path', Text)
270 248 version = Column('version', Integer)
271 249
@@ -1,26 +1,70 b''
1 1 """SQLAlchemy Metadata and Session object"""
2 2 from sqlalchemy.ext.declarative import declarative_base
3 from sqlalchemy.orm import scoped_session, sessionmaker
3 from sqlalchemy.orm import scoped_session, sessionmaker, class_mapper
4 from beaker import cache
5
4 6 from rhodecode.model import caching_query
5 from beaker import cache
7
6 8
7 9 # Beaker CacheManager. A home base for cache configurations.
8 10 cache_manager = cache.CacheManager()
9 11
10 12 __all__ = ['Base', 'Session']
11 13 #
12 14 # SQLAlchemy session manager. Updated by model.init_model()
13 15 #
14 16 Session = scoped_session(
15 17 sessionmaker(
16 18 query_cls=caching_query.query_callable(cache_manager)
17 19 )
18 20 )
19 21
22 class BaseModel(object):
23 """Base Model for all classess
24
25 """
26
27 @classmethod
28 def _get_keys(cls):
29 """return column names for this model """
30 return class_mapper(cls).c.keys()
31
32 def get_dict(self):
33 """return dict with keys and values corresponding
34 to this model data """
35
36 d = {}
37 for k in self._get_keys():
38 d[k] = getattr(self, k)
39 return d
40
41 def get_appstruct(self):
42 """return list with keys and values tupples corresponding
43 to this model data """
44
45 l = []
46 for k in self._get_keys():
47 l.append((k, getattr(self, k),))
48 return l
49
50 def populate_obj(self, populate_dict):
51 """populate model with data from given populate_dict"""
52
53 for k in self._get_keys():
54 if k in populate_dict:
55 setattr(self, k, populate_dict[k])
56
57 @classmethod
58 def query(cls):
59 return Session.query(cls)
60
61 @classmethod
62 def get(cls, id_):
63 return Session.query(cls).get(id_)
64
65
20 66 # The declarative Base
21 Base = declarative_base()
22 #For another db...
23 #Base2 = declarative_base()
67 Base = declarative_base(cls=BaseModel)
24 68
25 69 #to use cache use this in query
26 70 #.options(FromCache("sqlalchemy_cache_type", "cachekey"))
@@ -1,377 +1,378 b''
1 1 # -*- coding: utf-8 -*-
2 2 """
3 3 rhodecode.model.scm
4 4 ~~~~~~~~~~~~~~~~~~~
5 5
6 6 Scm model for RhodeCode
7 7
8 8 :created_on: Apr 9, 2010
9 9 :author: marcink
10 10 :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
11 11 :license: GPLv3, see COPYING for more details.
12 12 """
13 13 # This program is free software; you can redistribute it and/or
14 14 # modify it under the terms of the GNU General Public License
15 15 # as published by the Free Software Foundation; version 2
16 16 # of the License or (at your opinion) any later version of the license.
17 17 #
18 18 # This program is distributed in the hope that it will be useful,
19 19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 21 # GNU General Public License for more details.
22 22 #
23 23 # You should have received a copy of the GNU General Public License
24 24 # along with this program; if not, write to the Free Software
25 25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 26 # MA 02110-1301, USA.
27 27 import os
28 28 import time
29 29 import traceback
30 30 import logging
31 31
32 32 from vcs import get_backend
33 33 from vcs.utils.helpers import get_scm
34 34 from vcs.exceptions import RepositoryError, VCSError
35 35 from vcs.utils.lazy import LazyProperty
36 36
37 37 from mercurial import ui
38 38
39 39 from beaker.cache import cache_region, region_invalidate
40 40
41 41 from rhodecode import BACKENDS
42 42 from rhodecode.lib import helpers as h
43 43 from rhodecode.lib.auth import HasRepoPermissionAny
44 44 from rhodecode.lib.utils import get_repos, make_ui, action_logger
45 45 from rhodecode.model import BaseModel
46 46 from rhodecode.model.user import UserModel
47 47
48 48 from rhodecode.model.db import Repository, RhodeCodeUi, CacheInvalidation, \
49 49 UserFollowing, UserLog
50 50 from rhodecode.model.caching_query import FromCache
51 51
52 52 from sqlalchemy.orm import joinedload
53 53 from sqlalchemy.orm.session import make_transient
54 54 from sqlalchemy.exc import DatabaseError
55 55
56 56 log = logging.getLogger(__name__)
57 57
58 58
59 59 class UserTemp(object):
60 60 def __init__(self, user_id):
61 61 self.user_id = user_id
62 62 class RepoTemp(object):
63 63 def __init__(self, repo_id):
64 64 self.repo_id = repo_id
65 65
66 66 class ScmModel(BaseModel):
67 67 """Generic Scm Model
68 68 """
69 69
70 70 @LazyProperty
71 71 def repos_path(self):
72 72 """Get's the repositories root path from database
73 73 """
74 74
75 75 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
76 76
77 77 return q.ui_value
78 78
79 79 def repo_scan(self, repos_path, baseui):
80 80 """Listing of repositories in given path. This path should not be a
81 81 repository itself. Return a dictionary of repository objects
82 82
83 83 :param repos_path: path to directory containing repositories
84 84 :param baseui: baseui instance to instantiate MercurialRepostitory with
85 85 """
86 86
87 87 log.info('scanning for repositories in %s', repos_path)
88 88
89 89 if not isinstance(baseui, ui.ui):
90 90 baseui = make_ui('db')
91 91 repos_list = {}
92 92
93 93 for name, path in get_repos(repos_path):
94 94 try:
95 95 if repos_list.has_key(name):
96 96 raise RepositoryError('Duplicate repository name %s '
97 97 'found in %s' % (name, path))
98 98 else:
99 99
100 100 klass = get_backend(path[0])
101 101
102 102 if path[0] == 'hg' and path[0] in BACKENDS.keys():
103 103 repos_list[name] = klass(path[1], baseui=baseui)
104 104
105 105 if path[0] == 'git' and path[0] in BACKENDS.keys():
106 106 repos_list[name] = klass(path[1])
107 107 except OSError:
108 108 continue
109 109
110 110 return repos_list
111 111
112 112 def get_repos(self, all_repos=None):
113 113 """Get all repos from db and for each repo create it's backend instance.
114 114 and fill that backed with information from database
115 115
116 116 :param all_repos: give specific repositories list, good for filtering
117 117 """
118 118
119 119 if all_repos is None:
120 120 all_repos = self.sa.query(Repository)\
121 121 .order_by(Repository.repo_name).all()
122 122
123 123 #get the repositories that should be invalidated
124 124 invalidation_list = [str(x.cache_key) for x in \
125 125 self.sa.query(CacheInvalidation.cache_key)\
126 126 .filter(CacheInvalidation.cache_active == False)\
127 127 .all()]
128 128
129 129 for r in all_repos:
130 130
131 131 repo = self.get(r.repo_name, invalidation_list)
132 132
133 133 if repo is not None:
134 134 last_change = repo.last_change
135 135 tip = h.get_changeset_safe(repo, 'tip')
136 136
137 137 tmp_d = {}
138 138 tmp_d['name'] = repo.name
139 139 tmp_d['name_sort'] = tmp_d['name'].lower()
140 140 tmp_d['description'] = repo.dbrepo.description
141 141 tmp_d['description_sort'] = tmp_d['description']
142 142 tmp_d['last_change'] = last_change
143 143 tmp_d['last_change_sort'] = time.mktime(last_change.timetuple())
144 144 tmp_d['tip'] = tip.raw_id
145 145 tmp_d['tip_sort'] = tip.revision
146 146 tmp_d['rev'] = tip.revision
147 147 tmp_d['contact'] = repo.dbrepo.user.full_contact
148 148 tmp_d['contact_sort'] = tmp_d['contact']
149 tmp_d['owner_sort'] = tmp_d['contact']
149 150 tmp_d['repo_archives'] = list(repo._get_archives())
150 151 tmp_d['last_msg'] = tip.message
151 152 tmp_d['repo'] = repo
152 153 yield tmp_d
153 154
154 155 def get_repo(self, repo_name):
155 156 return self.get(repo_name)
156 157
157 158 def get(self, repo_name, invalidation_list=None):
158 159 """Get's repository from given name, creates BackendInstance and
159 160 propagates it's data from database with all additional information
160 161
161 162 :param repo_name:
162 163 :param invalidation_list: if a invalidation list is given the get
163 164 method should not manually check if this repository needs
164 165 invalidation and just invalidate the repositories in list
165 166
166 167 """
167 168 if not HasRepoPermissionAny('repository.read', 'repository.write',
168 169 'repository.admin')(repo_name, 'get repo check'):
169 170 return
170 171
171 172 #======================================================================
172 173 # CACHE FUNCTION
173 174 #======================================================================
174 175 @cache_region('long_term')
175 176 def _get_repo(repo_name):
176 177
177 178 repo_path = os.path.join(self.repos_path, repo_name)
178 179
179 180 try:
180 181 alias = get_scm(repo_path)[0]
181 182
182 183 log.debug('Creating instance of %s repository', alias)
183 184 backend = get_backend(alias)
184 185 except VCSError:
185 186 log.error(traceback.format_exc())
186 187 return
187 188
188 189 if alias == 'hg':
189 190 from pylons import app_globals as g
190 191 repo = backend(repo_path, create=False, baseui=g.baseui)
191 192 #skip hidden web repository
192 193 if repo._get_hidden():
193 194 return
194 195 else:
195 196 repo = backend(repo_path, create=False)
196 197
197 198 dbrepo = self.sa.query(Repository)\
198 199 .options(joinedload(Repository.fork))\
199 200 .options(joinedload(Repository.user))\
200 201 .filter(Repository.repo_name == repo_name)\
201 202 .scalar()
202 203
203 204 make_transient(dbrepo)
204 205 if dbrepo.user:
205 206 make_transient(dbrepo.user)
206 207 if dbrepo.fork:
207 208 make_transient(dbrepo.fork)
208 209
209 210 repo.dbrepo = dbrepo
210 211 return repo
211 212
212 213 pre_invalidate = True
213 214 if invalidation_list is not None:
214 215 pre_invalidate = repo_name in invalidation_list
215 216
216 217 if pre_invalidate:
217 218 invalidate = self._should_invalidate(repo_name)
218 219
219 220 if invalidate:
220 221 log.info('invalidating cache for repository %s', repo_name)
221 222 region_invalidate(_get_repo, None, repo_name)
222 223 self._mark_invalidated(invalidate)
223 224
224 225 return _get_repo(repo_name)
225 226
226 227
227 228
228 229 def mark_for_invalidation(self, repo_name):
229 230 """Puts cache invalidation task into db for
230 231 further global cache invalidation
231 232
232 233 :param repo_name: this repo that should invalidation take place
233 234 """
234 235
235 236 log.debug('marking %s for invalidation', repo_name)
236 237 cache = self.sa.query(CacheInvalidation)\
237 238 .filter(CacheInvalidation.cache_key == repo_name).scalar()
238 239
239 240 if cache:
240 241 #mark this cache as inactive
241 242 cache.cache_active = False
242 243 else:
243 244 log.debug('cache key not found in invalidation db -> creating one')
244 245 cache = CacheInvalidation(repo_name)
245 246
246 247 try:
247 248 self.sa.add(cache)
248 249 self.sa.commit()
249 250 except (DatabaseError,):
250 251 log.error(traceback.format_exc())
251 252 self.sa.rollback()
252 253
253 254
254 255 def toggle_following_repo(self, follow_repo_id, user_id):
255 256
256 257 f = self.sa.query(UserFollowing)\
257 258 .filter(UserFollowing.follows_repo_id == follow_repo_id)\
258 259 .filter(UserFollowing.user_id == user_id).scalar()
259 260
260 261 if f is not None:
261 262
262 263 try:
263 264 self.sa.delete(f)
264 265 self.sa.commit()
265 266 action_logger(UserTemp(user_id),
266 267 'stopped_following_repo',
267 268 RepoTemp(follow_repo_id))
268 269 return
269 270 except:
270 271 log.error(traceback.format_exc())
271 272 self.sa.rollback()
272 273 raise
273 274
274 275
275 276 try:
276 277 f = UserFollowing()
277 278 f.user_id = user_id
278 279 f.follows_repo_id = follow_repo_id
279 280 self.sa.add(f)
280 281 self.sa.commit()
281 282 action_logger(UserTemp(user_id),
282 283 'started_following_repo',
283 284 RepoTemp(follow_repo_id))
284 285 except:
285 286 log.error(traceback.format_exc())
286 287 self.sa.rollback()
287 288 raise
288 289
289 290 def toggle_following_user(self, follow_user_id , user_id):
290 291 f = self.sa.query(UserFollowing)\
291 292 .filter(UserFollowing.follows_user_id == follow_user_id)\
292 293 .filter(UserFollowing.user_id == user_id).scalar()
293 294
294 295 if f is not None:
295 296 try:
296 297 self.sa.delete(f)
297 298 self.sa.commit()
298 299 return
299 300 except:
300 301 log.error(traceback.format_exc())
301 302 self.sa.rollback()
302 303 raise
303 304
304 305 try:
305 306 f = UserFollowing()
306 307 f.user_id = user_id
307 308 f.follows_user_id = follow_user_id
308 309 self.sa.add(f)
309 310 self.sa.commit()
310 311 except:
311 312 log.error(traceback.format_exc())
312 313 self.sa.rollback()
313 314 raise
314 315
315 316 def is_following_repo(self, repo_name, user_id):
316 317 r = self.sa.query(Repository)\
317 318 .filter(Repository.repo_name == repo_name).scalar()
318 319
319 320 f = self.sa.query(UserFollowing)\
320 321 .filter(UserFollowing.follows_repository == r)\
321 322 .filter(UserFollowing.user_id == user_id).scalar()
322 323
323 324 return f is not None
324 325
325 326 def is_following_user(self, username, user_id):
326 327 u = UserModel(self.sa).get_by_username(username)
327 328
328 329 f = self.sa.query(UserFollowing)\
329 330 .filter(UserFollowing.follows_user == u)\
330 331 .filter(UserFollowing.user_id == user_id).scalar()
331 332
332 333 return f is not None
333 334
334 335 def get_followers(self, repo_id):
335 336 return self.sa.query(UserFollowing)\
336 337 .filter(UserFollowing.follows_repo_id == repo_id).count()
337 338
338 339 def get_forks(self, repo_id):
339 340 return self.sa.query(Repository)\
340 341 .filter(Repository.fork_id == repo_id).count()
341 342
342 343
343 344 def get_unread_journal(self):
344 345 return self.sa.query(UserLog).count()
345 346
346 347
347 348 def _should_invalidate(self, repo_name):
348 349 """Looks up database for invalidation signals for this repo_name
349 350
350 351 :param repo_name:
351 352 """
352 353
353 354 ret = self.sa.query(CacheInvalidation)\
354 355 .options(FromCache('sql_cache_short',
355 356 'get_invalidation_%s' % repo_name))\
356 357 .filter(CacheInvalidation.cache_key == repo_name)\
357 358 .filter(CacheInvalidation.cache_active == False)\
358 359 .scalar()
359 360
360 361 return ret
361 362
362 363 def _mark_invalidated(self, cache_key):
363 364 """ Marks all occurences of cache to invaldation as already invalidated
364 365
365 366 :param cache_key:
366 367 """
367 368
368 369 if cache_key:
369 370 log.debug('marking %s as already invalidated', cache_key)
370 371 try:
371 372 cache_key.cache_active = True
372 373 self.sa.add(cache_key)
373 374 self.sa.commit()
374 375 except (DatabaseError,):
375 376 log.error(traceback.format_exc())
376 377 self.sa.rollback()
377 378
@@ -1,104 +1,112 b''
1 1 import sys
2 from rhodecode import get_version
3 from rhodecode import __platform__
4
2 5 py_version = sys.version_info
3 6
4 from rhodecode import get_version
5
6 7 requirements = [
7 8 "Pylons==1.0.0",
8 9 "WebHelpers==1.2",
9 10 "SQLAlchemy==0.6.6",
10 "Mako==0.3.6",
11 "vcs==0.1.10",
12 "pygments==1.3.1",
11 "Mako==0.4.0",
12 "vcs==0.1.11",
13 "pygments==1.4.0",
13 14 "mercurial==1.7.5",
14 15 "whoosh==1.3.4",
15 "celery==2.1.4",
16 "py-bcrypt",
16 "celery==2.2.4",
17 17 "babel",
18 18 ]
19 19
20 20 classifiers = ['Development Status :: 5 - Production/Stable',
21 21 'Environment :: Web Environment',
22 22 'Framework :: Pylons',
23 23 'Intended Audience :: Developers',
24 24 'License :: OSI Approved :: BSD License',
25 25 'Operating System :: OS Independent',
26 26 'Programming Language :: Python', ]
27 27
28 if sys.version_info < (2, 6):
28 if py_version < (2, 6):
29 29 requirements.append("simplejson")
30 30 requirements.append("pysqlite")
31 31
32 if __platform__ in ('Linux', 'Darwin'):
33 requirements.append("py-bcrypt")
34
35
32 36 #additional files from project that goes somewhere in the filesystem
33 37 #relative to sys.prefix
34 38 data_files = []
35 39
36 40 #additional files that goes into package itself
37 41 package_data = {'rhodecode': ['i18n/*/LC_MESSAGES/*.mo', ], }
38 42
39 43 description = ('Mercurial repository browser/management with '
40 44 'build in push/pull server and full text search')
45 keywords = ' '.join (['rhodecode', 'rhodiumcode', 'mercurial', 'git',
46 'repository management', 'hgweb replacement'
47 'hgwebdir', 'gitweb replacement', 'serving hgweb',
48 ])
41 49 #long description
42 50 try:
43 51 readme_file = 'README.rst'
44 52 changelog_file = 'docs/changelog.rst'
45 53 long_description = open(readme_file).read() + '\n\n' + \
46 54 open(changelog_file).read()
47 55
48 56 except IOError, err:
49 57 sys.stderr.write("[WARNING] Cannot find file specified as "
50 58 "long_description (%s)\n or changelog (%s) skipping that file" \
51 59 % (readme_file, changelog_file))
52 60 long_description = description
53 61
54 62
55 63 try:
56 64 from setuptools import setup, find_packages
57 65 except ImportError:
58 66 from ez_setup import use_setuptools
59 67 use_setuptools()
60 68 from setuptools import setup, find_packages
61 69 #packages
62 70 packages = find_packages(exclude=['ez_setup'])
63 71
64 72 setup(
65 73 name='RhodeCode',
66 74 version=get_version(),
67 75 description=description,
68 76 long_description=long_description,
69 keywords='rhodiumcode mercurial web hgwebdir gitweb git replacement serving hgweb rhodecode',
77 keywords=keywords,
70 78 license='BSD',
71 79 author='Marcin Kuzminski',
72 80 author_email='marcin@python-works.com',
73 81 url='http://hg.python-works.com',
74 82 install_requires=requirements,
75 83 classifiers=classifiers,
76 84 setup_requires=["PasteScript>=1.6.3"],
77 85 data_files=data_files,
78 86 packages=packages,
79 87 include_package_data=True,
80 88 test_suite='nose.collector',
81 89 package_data=package_data,
82 90 message_extractors={'rhodecode': [
83 91 ('**.py', 'python', None),
84 92 ('templates/**.mako', 'mako', {'input_encoding': 'utf-8'}),
85 93 ('public/**', 'ignore', None)]},
86 94 zip_safe=False,
87 95 paster_plugins=['PasteScript', 'Pylons'],
88 96 entry_points="""
89 97 [paste.app_factory]
90 98 main = rhodecode.config.middleware:make_app
91 99
92 100 [paste.app_install]
93 101 main = pylons.util:PylonsInstaller
94 102
95 103 [paste.global_paster_command]
96 104 make-index = rhodecode.lib.indexers:MakeIndex
97 105 upgrade-db = rhodecode.lib.dbmigrate:UpgradeDb
98 106 celeryd=rhodecode.lib.celerypylons.commands:CeleryDaemonCommand
99 107 celerybeat=rhodecode.lib.celerypylons.commands:CeleryBeatCommand
100 108 camqadm=rhodecode.lib.celerypylons.commands:CAMQPAdminCommand
101 109 celeryev=rhodecode.lib.celerypylons.commands:CeleryEventCommand
102 110
103 111 """,
104 112 )
General Comments 0
You need to be logged in to leave comments. Login now