##// END OF EJS Templates
Merge with beta
marcink -
r1564:752b0a7b merge default
parent child Browse files
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -0,0 +1,132
1 #!/bin/sh
2 ########################################
3 #### THIS IS A REDHAT INIT.D SCRIPT ####
4 ########################################
5
6 ##################################################
7 #
8 # RhodeCode server startup script
9 # Recommended default-startup: 2 3 4 5
10 # Recommended default-stop: 0 1 6
11 #
12 ##################################################
13
14
15 APP_NAME="rhodecode"
16 # the location of your app
17 # since this is a web app, it should go in /var/www
18 APP_PATH="/var/www/$APP_NAME"
19
20 CONF_NAME="production.ini"
21
22 # write to wherever the PID should be stored, just ensure
23 # that the user you run paster as has the appropriate permissions
24 # same goes for the log file
25 PID_PATH="/var/run/rhodecode/pid"
26 LOG_PATH="/var/log/rhodecode/rhodecode.log"
27
28 # replace this with the path to the virtual environment you
29 # made for RhodeCode
30 PYTHON_PATH="/opt/python_virtualenvironments/rhodecode-venv"
31
32 RUN_AS="rhodecode"
33
34 DAEMON="$PYTHON_PATH/bin/paster"
35
36 DAEMON_OPTS="serve --daemon \
37 --user=$RUN_AS \
38 --group=$RUN_AS \
39 --pid-file=$PID_PATH \
40 --log-file=$LOG_PATH $APP_PATH/$CONF_NAME"
41
42 DESC="rhodecode-server"
43 LOCK_FILE="/var/lock/subsys/$APP_NAME"
44
45 # source CentOS init functions
46 . /etc/init.d/functions
47
48 RETVAL=0
49
50 remove_pid () {
51 rm -f ${PID_PATH}
52 rmdir `dirname ${PID_PATH}`
53 }
54
55 ensure_pid_dir () {
56 PID_DIR=`dirname ${PID_PATH}`
57 if [ ! -d ${PID_DIR} ] ; then
58 mkdir -p ${PID_DIR}
59 chown -R ${RUN_AS}:${RUN_AS} ${PID_DIR}
60 chmod 755 ${PID_DIR}
61 fi
62 }
63
64 start_rhodecode () {
65 ensure_pid_dir
66 PYTHON_EGG_CACHE="/tmp" daemon --pidfile $PID_PATH \
67 --user $RUN_AS "$DAEMON $DAEMON_OPTS"
68 RETVAL=$?
69 [ $RETVAL -eq 0 ] && touch $LOCK_FILE
70 return $RETVAL
71 }
72
73 stop_rhodecode () {
74 if [ -e $LOCK_FILE ]; then
75 killproc -p $PID_PATH
76 RETVAL=$?
77 rm -f $LOCK_FILE
78 rm -f $PID_PATH
79 else
80 RETVAL=1
81 fi
82 return $RETVAL
83 }
84
85 status_rhodecode() {
86 if [ -e $LOCK_FILE ]; then
87 # exit with non-zero to indicate failure
88 RETVAL=1
89 else
90 RETVAL=0
91 fi
92 return $RETVAL
93 }
94
95 restart_rhodecode () {
96 stop_rhodecode
97 start_rhodecode
98 RETVAL=$?
99 }
100
101 case "$1" in
102 start)
103 echo -n $"Starting $DESC: "
104 start_rhodecode
105 echo
106 ;;
107 stop)
108 echo -n $"Stopping $DESC: "
109 stop_rhodecode
110 echo
111 ;;
112 status)
113 status_rhodecode
114 RETVAL=$?
115 if [ ! $RETVAL -eq 0 ]; then
116 echo "RhodeCode server is running..."
117 else
118 echo "RhodeCode server is stopped."
119 fi
120 ;;
121 restart)
122 echo -n $"Restarting $DESC: "
123 restart_rhodecode
124 echo
125 ;;
126 *)
127 echo $"Usage: $0 {start|stop|restart|status}"
128 RETVAL=1
129 ;;
130 esac
131
132 exit $RETVAL No newline at end of file
1 NO CONTENT: new file 100755
NO CONTENT: new file 100755
The requested commit or file is too big and content was truncated. Show full diff
@@ -1,15 +1,15
1 List of contributors to RhodeCode project:
1 List of contributors to RhodeCode project:
2 Marcin Kuźmiński <marcin@python-works.com>
2 Marcin Kuźmiński <marcin@python-works.com>
3 Lukasz Balcerzak <lukaszbalcerzak@gmail.com>
3 Lukasz Balcerzak <lukaszbalcerzak@gmail.com>
4 Jason Harris <jason@jasonfharris.com>
4 Jason Harris <jason@jasonfharris.com>
5 Thayne Harbaugh <thayne@fusionio.com>
5 Thayne Harbaugh <thayne@fusionio.com>
6 cejones
6 cejones
7 Thomas Waldmann <tw-public@gmx.de>
7 Thomas Waldmann <tw-public@gmx.de>
8 Lorenzo M. Catucci <lorenzo@sancho.ccd.uniroma2.it>
8 Lorenzo M. Catucci <lorenzo@sancho.ccd.uniroma2.it>
9 Dmitri Kuznetsov
9 Dmitri Kuznetsov
10 Jared Bunting <jared.bunting@peachjean.com>
10 Jared Bunting <jared.bunting@peachjean.com>
11 Steve Romanow <slestak989@gmail.com>
11 Steve Romanow <slestak989@gmail.com>
12 Augosto Hermann <augusto.herrmann@planejamento.gov.br>
12 Augosto Hermann <augusto.herrmann@planejamento.gov.br>
13 Ankit Solanki <ankit.solanki@gmail.com>
13 Ankit Solanki <ankit.solanki@gmail.com>
14 Liad Shani <liadff@gmail.com>
14 Liad Shani <liadff@gmail.com>
15 No newline at end of file
15 Les Peabody <lpeabody@gmail.com>
@@ -1,354 +1,376
1 .. _changelog:
1 .. _changelog:
2
2
3 Changelog
3 Changelog
4 =========
4 =========
5
5
6
7 1.2.2 (**2011-10-17**)
8 ======================
9
10 news
11 ----
12
13 - #226 repo groups are available by path instead of numerical id
14
15 fixes
16 -----
17
18 - #259 Groups with the same name but with different parent group
19 - #260 Put repo in group, then move group to another group -> repo becomes unavailable
20 - #258 RhodeCode 1.2 assumes egg folder is writable (lockfiles problems)
21 - #265 ldap save fails sometimes on converting attributes to booleans,
22 added getter and setter into model that will prevent from this on db model level
23 - fixed problems with timestamps issues #251 and #213
24 - fixes #266 Rhodecode allows to create repo with the same name and in
25 the same parent as group
26 - fixes #245 Rescan of the repositories on Windows
27 - fixes #248 cannot edit repos inside a group on windows
28 - fixes #219 forking problems on windows
29
6 1.2.1 (**2011-10-08**)
30 1.2.1 (**2011-10-08**)
7 ======================
31 ======================
8
32
9 news
33 news
10 ----
34 ----
11
35
12
36
13 fixes
37 fixes
14 -----
38 -----
15
39
16 - fixed problems with basic auth and push problems
40 - fixed problems with basic auth and push problems
17 - gui fixes
41 - gui fixes
18 - fixed logger
42 - fixed logger
19
43
20
21 1.2.0 (**2011-10-07**)
44 1.2.0 (**2011-10-07**)
22 ======================
45 ======================
23
46
24
25 news
47 news
26 ----
48 ----
27
49
28 - implemented #47 repository groups
50 - implemented #47 repository groups
29 - implemented #89 Can setup google analytics code from settings menu
51 - implemented #89 Can setup google analytics code from settings menu
30 - implemented #91 added nicer looking archive urls with more download options
52 - implemented #91 added nicer looking archive urls with more download options
31 like tags, branches
53 like tags, branches
32 - implemented #44 into file browsing, and added follow branch option
54 - implemented #44 into file browsing, and added follow branch option
33 - implemented #84 downloads can be enabled/disabled for each repository
55 - implemented #84 downloads can be enabled/disabled for each repository
34 - anonymous repository can be cloned without having to pass default:default
56 - anonymous repository can be cloned without having to pass default:default
35 into clone url
57 into clone url
36 - fixed #90 whoosh indexer can index chooses repositories passed in command
58 - fixed #90 whoosh indexer can index chooses repositories passed in command
37 line
59 line
38 - extended journal with day aggregates and paging
60 - extended journal with day aggregates and paging
39 - implemented #107 source code lines highlight ranges
61 - implemented #107 source code lines highlight ranges
40 - implemented #93 customizable changelog on combined revision ranges -
62 - implemented #93 customizable changelog on combined revision ranges -
41 equivalent of githubs compare view
63 equivalent of githubs compare view
42 - implemented #108 extended and more powerful LDAP configuration
64 - implemented #108 extended and more powerful LDAP configuration
43 - implemented #56 users groups
65 - implemented #56 users groups
44 - major code rewrites optimized codes for speed and memory usage
66 - major code rewrites optimized codes for speed and memory usage
45 - raw and diff downloads are now in git format
67 - raw and diff downloads are now in git format
46 - setup command checks for write access to given path
68 - setup command checks for write access to given path
47 - fixed many issues with international characters and unicode. It uses utf8
69 - fixed many issues with international characters and unicode. It uses utf8
48 decode with replace to provide less errors even with non utf8 encoded strings
70 decode with replace to provide less errors even with non utf8 encoded strings
49 - #125 added API KEY access to feeds
71 - #125 added API KEY access to feeds
50 - #109 Repository can be created from external Mercurial link (aka. remote
72 - #109 Repository can be created from external Mercurial link (aka. remote
51 repository, and manually updated (via pull) from admin panel
73 repository, and manually updated (via pull) from admin panel
52 - beta git support - push/pull server + basic view for git repos
74 - beta git support - push/pull server + basic view for git repos
53 - added followers page and forks page
75 - added followers page and forks page
54 - server side file creation (with binary file upload interface)
76 - server side file creation (with binary file upload interface)
55 and edition with commits powered by codemirror
77 and edition with commits powered by codemirror
56 - #111 file browser file finder, quick lookup files on whole file tree
78 - #111 file browser file finder, quick lookup files on whole file tree
57 - added quick login sliding menu into main page
79 - added quick login sliding menu into main page
58 - changelog uses lazy loading of affected files details, in some scenarios
80 - changelog uses lazy loading of affected files details, in some scenarios
59 this can improve speed of changelog page dramatically especially for
81 this can improve speed of changelog page dramatically especially for
60 larger repositories.
82 larger repositories.
61 - implements #214 added support for downloading subrepos in download menu.
83 - implements #214 added support for downloading subrepos in download menu.
62 - Added basic API for direct operations on rhodecode via JSON
84 - Added basic API for direct operations on rhodecode via JSON
63 - Implemented advanced hook management
85 - Implemented advanced hook management
64
86
65 fixes
87 fixes
66 -----
88 -----
67
89
68 - fixed file browser bug, when switching into given form revision the url was
90 - fixed file browser bug, when switching into given form revision the url was
69 not changing
91 not changing
70 - fixed propagation to error controller on simplehg and simplegit middlewares
92 - fixed propagation to error controller on simplehg and simplegit middlewares
71 - fixed error when trying to make a download on empty repository
93 - fixed error when trying to make a download on empty repository
72 - fixed problem with '[' chars in commit messages in journal
94 - fixed problem with '[' chars in commit messages in journal
73 - fixed #99 Unicode errors, on file node paths with non utf-8 characters
95 - fixed #99 Unicode errors, on file node paths with non utf-8 characters
74 - journal fork fixes
96 - journal fork fixes
75 - removed issue with space inside renamed repository after deletion
97 - removed issue with space inside renamed repository after deletion
76 - fixed strange issue on formencode imports
98 - fixed strange issue on formencode imports
77 - fixed #126 Deleting repository on Windows, rename used incompatible chars.
99 - fixed #126 Deleting repository on Windows, rename used incompatible chars.
78 - #150 fixes for errors on repositories mapped in db but corrupted in
100 - #150 fixes for errors on repositories mapped in db but corrupted in
79 filesystem
101 filesystem
80 - fixed problem with ascendant characters in realm #181
102 - fixed problem with ascendant characters in realm #181
81 - fixed problem with sqlite file based database connection pool
103 - fixed problem with sqlite file based database connection pool
82 - whoosh indexer and code stats share the same dynamic extensions map
104 - whoosh indexer and code stats share the same dynamic extensions map
83 - fixes #188 - relationship delete of repo_to_perm entry on user removal
105 - fixes #188 - relationship delete of repo_to_perm entry on user removal
84 - fixes issue #189 Trending source files shows "show more" when no more exist
106 - fixes issue #189 Trending source files shows "show more" when no more exist
85 - fixes issue #197 Relative paths for pidlocks
107 - fixes issue #197 Relative paths for pidlocks
86 - fixes issue #198 password will require only 3 chars now for login form
108 - fixes issue #198 password will require only 3 chars now for login form
87 - fixes issue #199 wrong redirection for non admin users after creating a repository
109 - fixes issue #199 wrong redirection for non admin users after creating a repository
88 - fixes issues #202, bad db constraint made impossible to attach same group
110 - fixes issues #202, bad db constraint made impossible to attach same group
89 more than one time. Affects only mysql/postgres
111 more than one time. Affects only mysql/postgres
90 - fixes #218 os.kill patch for windows was missing sig param
112 - fixes #218 os.kill patch for windows was missing sig param
91 - improved rendering of dag (they are not trimmed anymore when number of
113 - improved rendering of dag (they are not trimmed anymore when number of
92 heads exceeds 5)
114 heads exceeds 5)
93
115
94 1.1.8 (**2011-04-12**)
116 1.1.8 (**2011-04-12**)
95 ======================
117 ======================
96
118
97 news
119 news
98 ----
120 ----
99
121
100 - improved windows support
122 - improved windows support
101
123
102 fixes
124 fixes
103 -----
125 -----
104
126
105 - fixed #140 freeze of python dateutil library, since new version is python2.x
127 - fixed #140 freeze of python dateutil library, since new version is python2.x
106 incompatible
128 incompatible
107 - setup-app will check for write permission in given path
129 - setup-app will check for write permission in given path
108 - cleaned up license info issue #149
130 - cleaned up license info issue #149
109 - fixes for issues #137,#116 and problems with unicode and accented characters.
131 - fixes for issues #137,#116 and problems with unicode and accented characters.
110 - fixes crashes on gravatar, when passed in email as unicode
132 - fixes crashes on gravatar, when passed in email as unicode
111 - fixed tooltip flickering problems
133 - fixed tooltip flickering problems
112 - fixed came_from redirection on windows
134 - fixed came_from redirection on windows
113 - fixed logging modules, and sql formatters
135 - fixed logging modules, and sql formatters
114 - windows fixes for os.kill issue #133
136 - windows fixes for os.kill issue #133
115 - fixes path splitting for windows issues #148
137 - fixes path splitting for windows issues #148
116 - fixed issue #143 wrong import on migration to 1.1.X
138 - fixed issue #143 wrong import on migration to 1.1.X
117 - fixed problems with displaying binary files, thanks to Thomas Waldmann
139 - fixed problems with displaying binary files, thanks to Thomas Waldmann
118 - removed name from archive files since it's breaking ui for long repo names
140 - removed name from archive files since it's breaking ui for long repo names
119 - fixed issue with archive headers sent to browser, thanks to Thomas Waldmann
141 - fixed issue with archive headers sent to browser, thanks to Thomas Waldmann
120 - fixed compatibility for 1024px displays, and larger dpi settings, thanks to
142 - fixed compatibility for 1024px displays, and larger dpi settings, thanks to
121 Thomas Waldmann
143 Thomas Waldmann
122 - fixed issue #166 summary pager was skipping 10 revisions on second page
144 - fixed issue #166 summary pager was skipping 10 revisions on second page
123
145
124
146
125 1.1.7 (**2011-03-23**)
147 1.1.7 (**2011-03-23**)
126 ======================
148 ======================
127
149
128 news
150 news
129 ----
151 ----
130
152
131 fixes
153 fixes
132 -----
154 -----
133
155
134 - fixed (again) #136 installation support for FreeBSD
156 - fixed (again) #136 installation support for FreeBSD
135
157
136
158
137 1.1.6 (**2011-03-21**)
159 1.1.6 (**2011-03-21**)
138 ======================
160 ======================
139
161
140 news
162 news
141 ----
163 ----
142
164
143 fixes
165 fixes
144 -----
166 -----
145
167
146 - fixed #136 installation support for FreeBSD
168 - fixed #136 installation support for FreeBSD
147 - RhodeCode will check for python version during installation
169 - RhodeCode will check for python version during installation
148
170
149 1.1.5 (**2011-03-17**)
171 1.1.5 (**2011-03-17**)
150 ======================
172 ======================
151
173
152 news
174 news
153 ----
175 ----
154
176
155 - basic windows support, by exchanging pybcrypt into sha256 for windows only
177 - basic windows support, by exchanging pybcrypt into sha256 for windows only
156 highly inspired by idea of mantis406
178 highly inspired by idea of mantis406
157
179
158 fixes
180 fixes
159 -----
181 -----
160
182
161 - fixed sorting by author in main page
183 - fixed sorting by author in main page
162 - fixed crashes with diffs on binary files
184 - fixed crashes with diffs on binary files
163 - fixed #131 problem with boolean values for LDAP
185 - fixed #131 problem with boolean values for LDAP
164 - fixed #122 mysql problems thanks to striker69
186 - fixed #122 mysql problems thanks to striker69
165 - fixed problem with errors on calling raw/raw_files/annotate functions
187 - fixed problem with errors on calling raw/raw_files/annotate functions
166 with unknown revisions
188 with unknown revisions
167 - fixed returned rawfiles attachment names with international character
189 - fixed returned rawfiles attachment names with international character
168 - cleaned out docs, big thanks to Jason Harris
190 - cleaned out docs, big thanks to Jason Harris
169
191
170 1.1.4 (**2011-02-19**)
192 1.1.4 (**2011-02-19**)
171 ======================
193 ======================
172
194
173 news
195 news
174 ----
196 ----
175
197
176 fixes
198 fixes
177 -----
199 -----
178
200
179 - fixed formencode import problem on settings page, that caused server crash
201 - fixed formencode import problem on settings page, that caused server crash
180 when that page was accessed as first after server start
202 when that page was accessed as first after server start
181 - journal fixes
203 - journal fixes
182 - fixed option to access repository just by entering http://server/<repo_name>
204 - fixed option to access repository just by entering http://server/<repo_name>
183
205
184 1.1.3 (**2011-02-16**)
206 1.1.3 (**2011-02-16**)
185 ======================
207 ======================
186
208
187 news
209 news
188 ----
210 ----
189
211
190 - implemented #102 allowing the '.' character in username
212 - implemented #102 allowing the '.' character in username
191 - added option to access repository just by entering http://server/<repo_name>
213 - added option to access repository just by entering http://server/<repo_name>
192 - celery task ignores result for better performance
214 - celery task ignores result for better performance
193
215
194 fixes
216 fixes
195 -----
217 -----
196
218
197 - fixed ehlo command and non auth mail servers on smtp_lib. Thanks to
219 - fixed ehlo command and non auth mail servers on smtp_lib. Thanks to
198 apollo13 and Johan Walles
220 apollo13 and Johan Walles
199 - small fixes in journal
221 - small fixes in journal
200 - fixed problems with getting setting for celery from .ini files
222 - fixed problems with getting setting for celery from .ini files
201 - registration, password reset and login boxes share the same title as main
223 - registration, password reset and login boxes share the same title as main
202 application now
224 application now
203 - fixed #113: to high permissions to fork repository
225 - fixed #113: to high permissions to fork repository
204 - fixed problem with '[' chars in commit messages in journal
226 - fixed problem with '[' chars in commit messages in journal
205 - removed issue with space inside renamed repository after deletion
227 - removed issue with space inside renamed repository after deletion
206 - db transaction fixes when filesystem repository creation failed
228 - db transaction fixes when filesystem repository creation failed
207 - fixed #106 relation issues on databases different than sqlite
229 - fixed #106 relation issues on databases different than sqlite
208 - fixed static files paths links to use of url() method
230 - fixed static files paths links to use of url() method
209
231
210 1.1.2 (**2011-01-12**)
232 1.1.2 (**2011-01-12**)
211 ======================
233 ======================
212
234
213 news
235 news
214 ----
236 ----
215
237
216
238
217 fixes
239 fixes
218 -----
240 -----
219
241
220 - fixes #98 protection against float division of percentage stats
242 - fixes #98 protection against float division of percentage stats
221 - fixed graph bug
243 - fixed graph bug
222 - forced webhelpers version since it was making troubles during installation
244 - forced webhelpers version since it was making troubles during installation
223
245
224 1.1.1 (**2011-01-06**)
246 1.1.1 (**2011-01-06**)
225 ======================
247 ======================
226
248
227 news
249 news
228 ----
250 ----
229
251
230 - added force https option into ini files for easier https usage (no need to
252 - added force https option into ini files for easier https usage (no need to
231 set server headers with this options)
253 set server headers with this options)
232 - small css updates
254 - small css updates
233
255
234 fixes
256 fixes
235 -----
257 -----
236
258
237 - fixed #96 redirect loop on files view on repositories without changesets
259 - fixed #96 redirect loop on files view on repositories without changesets
238 - fixed #97 unicode string passed into server header in special cases (mod_wsgi)
260 - fixed #97 unicode string passed into server header in special cases (mod_wsgi)
239 and server crashed with errors
261 and server crashed with errors
240 - fixed large tooltips problems on main page
262 - fixed large tooltips problems on main page
241 - fixed #92 whoosh indexer is more error proof
263 - fixed #92 whoosh indexer is more error proof
242
264
243 1.1.0 (**2010-12-18**)
265 1.1.0 (**2010-12-18**)
244 ======================
266 ======================
245
267
246 news
268 news
247 ----
269 ----
248
270
249 - rewrite of internals for vcs >=0.1.10
271 - rewrite of internals for vcs >=0.1.10
250 - uses mercurial 1.7 with dotencode disabled for maintaining compatibility
272 - uses mercurial 1.7 with dotencode disabled for maintaining compatibility
251 with older clients
273 with older clients
252 - anonymous access, authentication via ldap
274 - anonymous access, authentication via ldap
253 - performance upgrade for cached repos list - each repository has it's own
275 - performance upgrade for cached repos list - each repository has it's own
254 cache that's invalidated when needed.
276 cache that's invalidated when needed.
255 - performance upgrades on repositories with large amount of commits (20K+)
277 - performance upgrades on repositories with large amount of commits (20K+)
256 - main page quick filter for filtering repositories
278 - main page quick filter for filtering repositories
257 - user dashboards with ability to follow chosen repositories actions
279 - user dashboards with ability to follow chosen repositories actions
258 - sends email to admin on new user registration
280 - sends email to admin on new user registration
259 - added cache/statistics reset options into repository settings
281 - added cache/statistics reset options into repository settings
260 - more detailed action logger (based on hooks) with pushed changesets lists
282 - more detailed action logger (based on hooks) with pushed changesets lists
261 and options to disable those hooks from admin panel
283 and options to disable those hooks from admin panel
262 - introduced new enhanced changelog for merges that shows more accurate results
284 - introduced new enhanced changelog for merges that shows more accurate results
263 - new improved and faster code stats (based on pygments lexers mapping tables,
285 - new improved and faster code stats (based on pygments lexers mapping tables,
264 showing up to 10 trending sources for each repository. Additionally stats
286 showing up to 10 trending sources for each repository. Additionally stats
265 can be disabled in repository settings.
287 can be disabled in repository settings.
266 - gui optimizations, fixed application width to 1024px
288 - gui optimizations, fixed application width to 1024px
267 - added cut off (for large files/changesets) limit into config files
289 - added cut off (for large files/changesets) limit into config files
268 - whoosh, celeryd, upgrade moved to paster command
290 - whoosh, celeryd, upgrade moved to paster command
269 - other than sqlite database backends can be used
291 - other than sqlite database backends can be used
270
292
271 fixes
293 fixes
272 -----
294 -----
273
295
274 - fixes #61 forked repo was showing only after cache expired
296 - fixes #61 forked repo was showing only after cache expired
275 - fixes #76 no confirmation on user deletes
297 - fixes #76 no confirmation on user deletes
276 - fixes #66 Name field misspelled
298 - fixes #66 Name field misspelled
277 - fixes #72 block user removal when he owns repositories
299 - fixes #72 block user removal when he owns repositories
278 - fixes #69 added password confirmation fields
300 - fixes #69 added password confirmation fields
279 - fixes #87 RhodeCode crashes occasionally on updating repository owner
301 - fixes #87 RhodeCode crashes occasionally on updating repository owner
280 - fixes #82 broken annotations on files with more than 1 blank line at the end
302 - fixes #82 broken annotations on files with more than 1 blank line at the end
281 - a lot of fixes and tweaks for file browser
303 - a lot of fixes and tweaks for file browser
282 - fixed detached session issues
304 - fixed detached session issues
283 - fixed when user had no repos he would see all repos listed in my account
305 - fixed when user had no repos he would see all repos listed in my account
284 - fixed ui() instance bug when global hgrc settings was loaded for server
306 - fixed ui() instance bug when global hgrc settings was loaded for server
285 instance and all hgrc options were merged with our db ui() object
307 instance and all hgrc options were merged with our db ui() object
286 - numerous small bugfixes
308 - numerous small bugfixes
287
309
288 (special thanks for TkSoh for detailed feedback)
310 (special thanks for TkSoh for detailed feedback)
289
311
290
312
291 1.0.2 (**2010-11-12**)
313 1.0.2 (**2010-11-12**)
292 ======================
314 ======================
293
315
294 news
316 news
295 ----
317 ----
296
318
297 - tested under python2.7
319 - tested under python2.7
298 - bumped sqlalchemy and celery versions
320 - bumped sqlalchemy and celery versions
299
321
300 fixes
322 fixes
301 -----
323 -----
302
324
303 - fixed #59 missing graph.js
325 - fixed #59 missing graph.js
304 - fixed repo_size crash when repository had broken symlinks
326 - fixed repo_size crash when repository had broken symlinks
305 - fixed python2.5 crashes.
327 - fixed python2.5 crashes.
306
328
307
329
308 1.0.1 (**2010-11-10**)
330 1.0.1 (**2010-11-10**)
309 ======================
331 ======================
310
332
311 news
333 news
312 ----
334 ----
313
335
314 - small css updated
336 - small css updated
315
337
316 fixes
338 fixes
317 -----
339 -----
318
340
319 - fixed #53 python2.5 incompatible enumerate calls
341 - fixed #53 python2.5 incompatible enumerate calls
320 - fixed #52 disable mercurial extension for web
342 - fixed #52 disable mercurial extension for web
321 - fixed #51 deleting repositories don't delete it's dependent objects
343 - fixed #51 deleting repositories don't delete it's dependent objects
322
344
323
345
324 1.0.0 (**2010-11-02**)
346 1.0.0 (**2010-11-02**)
325 ======================
347 ======================
326
348
327 - security bugfix simplehg wasn't checking for permissions on commands
349 - security bugfix simplehg wasn't checking for permissions on commands
328 other than pull or push.
350 other than pull or push.
329 - fixed doubled messages after push or pull in admin journal
351 - fixed doubled messages after push or pull in admin journal
330 - templating and css corrections, fixed repo switcher on chrome, updated titles
352 - templating and css corrections, fixed repo switcher on chrome, updated titles
331 - admin menu accessible from options menu on repository view
353 - admin menu accessible from options menu on repository view
332 - permissions cached queries
354 - permissions cached queries
333
355
334 1.0.0rc4 (**2010-10-12**)
356 1.0.0rc4 (**2010-10-12**)
335 ==========================
357 ==========================
336
358
337 - fixed python2.5 missing simplejson imports (thanks to Jens Bäckman)
359 - fixed python2.5 missing simplejson imports (thanks to Jens Bäckman)
338 - removed cache_manager settings from sqlalchemy meta
360 - removed cache_manager settings from sqlalchemy meta
339 - added sqlalchemy cache settings to ini files
361 - added sqlalchemy cache settings to ini files
340 - validated password length and added second try of failure on paster setup-app
362 - validated password length and added second try of failure on paster setup-app
341 - fixed setup database destroy prompt even when there was no db
363 - fixed setup database destroy prompt even when there was no db
342
364
343
365
344 1.0.0rc3 (**2010-10-11**)
366 1.0.0rc3 (**2010-10-11**)
345 =========================
367 =========================
346
368
347 - fixed i18n during installation.
369 - fixed i18n during installation.
348
370
349 1.0.0rc2 (**2010-10-11**)
371 1.0.0rc2 (**2010-10-11**)
350 =========================
372 =========================
351
373
352 - Disabled dirsize in file browser, it's causing nasty bug when dir renames
374 - Disabled dirsize in file browser, it's causing nasty bug when dir renames
353 occure. After vcs is fixed it'll be put back again.
375 occure. After vcs is fixed it'll be put back again.
354 - templating/css rewrites, optimized css. No newline at end of file
376 - templating/css rewrites, optimized css.
@@ -1,573 +1,605
1 .. _setup:
1 .. _setup:
2
2
3 Setup
3 Setup
4 =====
4 =====
5
5
6
6
7 Setting up RhodeCode
7 Setting up RhodeCode
8 --------------------
8 --------------------
9
9
10 First, you will need to create a RhodeCode configuration file. Run the
10 First, you will need to create a RhodeCode configuration file. Run the
11 following command to do this::
11 following command to do this::
12
12
13 paster make-config RhodeCode production.ini
13 paster make-config RhodeCode production.ini
14
14
15 - This will create the file `production.ini` in the current directory. This
15 - This will create the file `production.ini` in the current directory. This
16 configuration file contains the various settings for RhodeCode, e.g proxy
16 configuration file contains the various settings for RhodeCode, e.g proxy
17 port, email settings, usage of static files, cache, celery settings and
17 port, email settings, usage of static files, cache, celery settings and
18 logging.
18 logging.
19
19
20
20
21 Next, you need to create the databases used by RhodeCode. I recommend that you
21 Next, you need to create the databases used by RhodeCode. I recommend that you
22 use sqlite (default) or postgresql. If you choose a database other than the
22 use sqlite (default) or postgresql. If you choose a database other than the
23 default ensure you properly adjust the db url in your production.ini
23 default ensure you properly adjust the db url in your production.ini
24 configuration file to use this other database. Create the databases by running
24 configuration file to use this other database. Create the databases by running
25 the following command::
25 the following command::
26
26
27 paster setup-app production.ini
27 paster setup-app production.ini
28
28
29 This will prompt you for a "root" path. This "root" path is the location where
29 This will prompt you for a "root" path. This "root" path is the location where
30 RhodeCode will store all of its repositories on the current machine. After
30 RhodeCode will store all of its repositories on the current machine. After
31 entering this "root" path ``setup-app`` will also prompt you for a username
31 entering this "root" path ``setup-app`` will also prompt you for a username
32 and password for the initial admin account which ``setup-app`` sets up for you.
32 and password for the initial admin account which ``setup-app`` sets up for you.
33
33
34 - The ``setup-app`` command will create all of the needed tables and an admin
34 - The ``setup-app`` command will create all of the needed tables and an admin
35 account. When choosing a root path you can either use a new empty location,
35 account. When choosing a root path you can either use a new empty location,
36 or a location which already contains existing repositories. If you choose a
36 or a location which already contains existing repositories. If you choose a
37 location which contains existing repositories RhodeCode will simply add all
37 location which contains existing repositories RhodeCode will simply add all
38 of the repositories at the chosen location to it's database. (Note: make
38 of the repositories at the chosen location to it's database. (Note: make
39 sure you specify the correct path to the root).
39 sure you specify the correct path to the root).
40 - Note: the given path for mercurial_ repositories **must** be write accessible
40 - Note: the given path for mercurial_ repositories **must** be write accessible
41 for the application. It's very important since the RhodeCode web interface
41 for the application. It's very important since the RhodeCode web interface
42 will work without write access, but when trying to do a push it will
42 will work without write access, but when trying to do a push it will
43 eventually fail with permission denied errors unless it has write access.
43 eventually fail with permission denied errors unless it has write access.
44
44
45 You are now ready to use RhodeCode, to run it simply execute::
45 You are now ready to use RhodeCode, to run it simply execute::
46
46
47 paster serve production.ini
47 paster serve production.ini
48
48
49 - This command runs the RhodeCode server. The web app should be available at the
49 - This command runs the RhodeCode server. The web app should be available at the
50 127.0.0.1:5000. This ip and port is configurable via the production.ini
50 127.0.0.1:5000. This ip and port is configurable via the production.ini
51 file created in previous step
51 file created in previous step
52 - Use the admin account you created above when running ``setup-app`` to login
52 - Use the admin account you created above when running ``setup-app`` to login
53 to the web app.
53 to the web app.
54 - The default permissions on each repository is read, and the owner is admin.
54 - The default permissions on each repository is read, and the owner is admin.
55 Remember to update these if needed.
55 Remember to update these if needed.
56 - In the admin panel you can toggle ldap, anonymous, permissions settings. As
56 - In the admin panel you can toggle ldap, anonymous, permissions settings. As
57 well as edit more advanced options on users and repositories
57 well as edit more advanced options on users and repositories
58
58
59 Try copying your own mercurial repository into the "root" directory you are
59 Try copying your own mercurial repository into the "root" directory you are
60 using, then from within the RhodeCode web application choose Admin >
60 using, then from within the RhodeCode web application choose Admin >
61 repositories. Then choose Add New Repository. Add the repository you copied
61 repositories. Then choose Add New Repository. Add the repository you copied
62 into the root. Test that you can browse your repository from within RhodeCode
62 into the root. Test that you can browse your repository from within RhodeCode
63 and then try cloning your repository from RhodeCode with::
63 and then try cloning your repository from RhodeCode with::
64
64
65 hg clone http://127.0.0.1:5000/<repository name>
65 hg clone http://127.0.0.1:5000/<repository name>
66
66
67 where *repository name* is replaced by the name of your repository.
67 where *repository name* is replaced by the name of your repository.
68
68
69 Using RhodeCode with SSH
69 Using RhodeCode with SSH
70 ------------------------
70 ------------------------
71
71
72 RhodeCode currently only hosts repositories using http and https. (The addition
72 RhodeCode currently only hosts repositories using http and https. (The addition
73 of ssh hosting is a planned future feature.) However you can easily use ssh in
73 of ssh hosting is a planned future feature.) However you can easily use ssh in
74 parallel with RhodeCode. (Repository access via ssh is a standard "out of
74 parallel with RhodeCode. (Repository access via ssh is a standard "out of
75 the box" feature of mercurial_ and you can use this to access any of the
75 the box" feature of mercurial_ and you can use this to access any of the
76 repositories that RhodeCode is hosting. See PublishingRepositories_)
76 repositories that RhodeCode is hosting. See PublishingRepositories_)
77
77
78 RhodeCode repository structures are kept in directories with the same name
78 RhodeCode repository structures are kept in directories with the same name
79 as the project. When using repository groups, each group is a subdirectory.
79 as the project. When using repository groups, each group is a subdirectory.
80 This allows you to easily use ssh for accessing repositories.
80 This allows you to easily use ssh for accessing repositories.
81
81
82 In order to use ssh you need to make sure that your web-server and the users
82 In order to use ssh you need to make sure that your web-server and the users
83 login accounts have the correct permissions set on the appropriate directories.
83 login accounts have the correct permissions set on the appropriate directories.
84 (Note that these permissions are independent of any permissions you have set up
84 (Note that these permissions are independent of any permissions you have set up
85 using the RhodeCode web interface.)
85 using the RhodeCode web interface.)
86
86
87 If your main directory (the same as set in RhodeCode settings) is for example
87 If your main directory (the same as set in RhodeCode settings) is for example
88 set to **/home/hg** and the repository you are using is named `rhodecode`, then
88 set to **/home/hg** and the repository you are using is named `rhodecode`, then
89 to clone via ssh you should run::
89 to clone via ssh you should run::
90
90
91 hg clone ssh://user@server.com/home/hg/rhodecode
91 hg clone ssh://user@server.com/home/hg/rhodecode
92
92
93 Using other external tools such as mercurial-server_ or using ssh key based
93 Using other external tools such as mercurial-server_ or using ssh key based
94 authentication is fully supported.
94 authentication is fully supported.
95
95
96 Note: In an advanced setup, in order for your ssh access to use the same
96 Note: In an advanced setup, in order for your ssh access to use the same
97 permissions as set up via the RhodeCode web interface, you can create an
97 permissions as set up via the RhodeCode web interface, you can create an
98 authentication hook to connect to the rhodecode db and runs check functions for
98 authentication hook to connect to the rhodecode db and runs check functions for
99 permissions against that.
99 permissions against that.
100
100
101 Setting up Whoosh full text search
101 Setting up Whoosh full text search
102 ----------------------------------
102 ----------------------------------
103
103
104 Starting from version 1.1 the whoosh index can be build by using the paster
104 Starting from version 1.1 the whoosh index can be build by using the paster
105 command ``make-index``. To use ``make-index`` you must specify the configuration
105 command ``make-index``. To use ``make-index`` you must specify the configuration
106 file that stores the location of the index. You may specify the location of the
106 file that stores the location of the index. You may specify the location of the
107 repositories (`--repo-location`). If not specified, this value is retrieved
107 repositories (`--repo-location`). If not specified, this value is retrieved
108 from the RhodeCode database. This was required prior to 1.2. Starting from
108 from the RhodeCode database. This was required prior to 1.2. Starting from
109 version 1.2 it is also possible to specify a comma separated list of
109 version 1.2 it is also possible to specify a comma separated list of
110 repositories (`--index-only`) to build index only on chooses repositories
110 repositories (`--index-only`) to build index only on chooses repositories
111 skipping any other found in repos location
111 skipping any other found in repos location
112
112
113 You may optionally pass the option `-f` to enable a full index rebuild. Without
113 You may optionally pass the option `-f` to enable a full index rebuild. Without
114 the `-f` option, indexing will run always in "incremental" mode.
114 the `-f` option, indexing will run always in "incremental" mode.
115
115
116 For an incremental index build use::
116 For an incremental index build use::
117
117
118 paster make-index production.ini
118 paster make-index production.ini
119
119
120 For a full index rebuild use::
120 For a full index rebuild use::
121
121
122 paster make-index production.ini -f
122 paster make-index production.ini -f
123
123
124
124
125 building index just for chosen repositories is possible with such command::
125 building index just for chosen repositories is possible with such command::
126
126
127 paster make-index production.ini --index-only=vcs,rhodecode
127 paster make-index production.ini --index-only=vcs,rhodecode
128
128
129
129
130 In order to do periodical index builds and keep your index always up to date.
130 In order to do periodical index builds and keep your index always up to date.
131 It's recommended to do a crontab entry for incremental indexing.
131 It's recommended to do a crontab entry for incremental indexing.
132 An example entry might look like this::
132 An example entry might look like this::
133
133
134 /path/to/python/bin/paster make-index /path/to/rhodecode/production.ini
134 /path/to/python/bin/paster make-index /path/to/rhodecode/production.ini
135
135
136 When using incremental mode (the default) whoosh will check the last
136 When using incremental mode (the default) whoosh will check the last
137 modification date of each file and add it to be reindexed if a newer file is
137 modification date of each file and add it to be reindexed if a newer file is
138 available. The indexing daemon checks for any removed files and removes them
138 available. The indexing daemon checks for any removed files and removes them
139 from index.
139 from index.
140
140
141 If you want to rebuild index from scratch, you can use the `-f` flag as above,
141 If you want to rebuild index from scratch, you can use the `-f` flag as above,
142 or in the admin panel you can check `build from scratch` flag.
142 or in the admin panel you can check `build from scratch` flag.
143
143
144
144
145 Setting up LDAP support
145 Setting up LDAP support
146 -----------------------
146 -----------------------
147
147
148 RhodeCode starting from version 1.1 supports ldap authentication. In order
148 RhodeCode starting from version 1.1 supports ldap authentication. In order
149 to use LDAP, you have to install the python-ldap_ package. This package is
149 to use LDAP, you have to install the python-ldap_ package. This package is
150 available via pypi, so you can install it by running
150 available via pypi, so you can install it by running
151
151
152 using easy_install::
152 using easy_install::
153
153
154 easy_install python-ldap
154 easy_install python-ldap
155
155
156 using pip::
156 using pip::
157
157
158 pip install python-ldap
158 pip install python-ldap
159
159
160 .. note::
160 .. note::
161 python-ldap requires some certain libs on your system, so before installing
161 python-ldap requires some certain libs on your system, so before installing
162 it check that you have at least `openldap`, and `sasl` libraries.
162 it check that you have at least `openldap`, and `sasl` libraries.
163
163
164 LDAP settings are located in admin->ldap section,
164 LDAP settings are located in admin->ldap section,
165
165
166 Here's a typical ldap setup::
166 Here's a typical ldap setup::
167
167
168 Connection settings
168 Connection settings
169 Enable LDAP = checked
169 Enable LDAP = checked
170 Host = host.example.org
170 Host = host.example.org
171 Port = 389
171 Port = 389
172 Account = <account>
172 Account = <account>
173 Password = <password>
173 Password = <password>
174 Connection Security = LDAPS connection
174 Connection Security = LDAPS connection
175 Certificate Checks = DEMAND
175 Certificate Checks = DEMAND
176
176
177 Search settings
177 Search settings
178 Base DN = CN=users,DC=host,DC=example,DC=org
178 Base DN = CN=users,DC=host,DC=example,DC=org
179 LDAP Filter = (&(objectClass=user)(!(objectClass=computer)))
179 LDAP Filter = (&(objectClass=user)(!(objectClass=computer)))
180 LDAP Search Scope = SUBTREE
180 LDAP Search Scope = SUBTREE
181
181
182 Attribute mappings
182 Attribute mappings
183 Login Attribute = uid
183 Login Attribute = uid
184 First Name Attribute = firstName
184 First Name Attribute = firstName
185 Last Name Attribute = lastName
185 Last Name Attribute = lastName
186 E-mail Attribute = mail
186 E-mail Attribute = mail
187
187
188 .. _enable_ldap:
188 .. _enable_ldap:
189
189
190 Enable LDAP : required
190 Enable LDAP : required
191 Whether to use LDAP for authenticating users.
191 Whether to use LDAP for authenticating users.
192
192
193 .. _ldap_host:
193 .. _ldap_host:
194
194
195 Host : required
195 Host : required
196 LDAP server hostname or IP address.
196 LDAP server hostname or IP address.
197
197
198 .. _Port:
198 .. _Port:
199
199
200 Port : required
200 Port : required
201 389 for un-encrypted LDAP, 636 for SSL-encrypted LDAP.
201 389 for un-encrypted LDAP, 636 for SSL-encrypted LDAP.
202
202
203 .. _ldap_account:
203 .. _ldap_account:
204
204
205 Account : optional
205 Account : optional
206 Only required if the LDAP server does not allow anonymous browsing of
206 Only required if the LDAP server does not allow anonymous browsing of
207 records. This should be a special account for record browsing. This
207 records. This should be a special account for record browsing. This
208 will require `LDAP Password`_ below.
208 will require `LDAP Password`_ below.
209
209
210 .. _LDAP Password:
210 .. _LDAP Password:
211
211
212 Password : optional
212 Password : optional
213 Only required if the LDAP server does not allow anonymous browsing of
213 Only required if the LDAP server does not allow anonymous browsing of
214 records.
214 records.
215
215
216 .. _Enable LDAPS:
216 .. _Enable LDAPS:
217
217
218 Connection Security : required
218 Connection Security : required
219 Defines the connection to LDAP server
219 Defines the connection to LDAP server
220
220
221 No encryption
221 No encryption
222 Plain non encrypted connection
222 Plain non encrypted connection
223
223
224 LDAPS connection
224 LDAPS connection
225 Enable ldaps connection. It will likely require `Port`_ to be set to
225 Enable ldaps connection. It will likely require `Port`_ to be set to
226 a different value (standard LDAPS port is 636). When LDAPS is enabled
226 a different value (standard LDAPS port is 636). When LDAPS is enabled
227 then `Certificate Checks`_ is required.
227 then `Certificate Checks`_ is required.
228
228
229 START_TLS on LDAP connection
229 START_TLS on LDAP connection
230 START TLS connection
230 START TLS connection
231
231
232 .. _Certificate Checks:
232 .. _Certificate Checks:
233
233
234 Certificate Checks : optional
234 Certificate Checks : optional
235 How SSL certificates verification is handled - this is only useful when
235 How SSL certificates verification is handled - this is only useful when
236 `Enable LDAPS`_ is enabled. Only DEMAND or HARD offer full SSL security
236 `Enable LDAPS`_ is enabled. Only DEMAND or HARD offer full SSL security
237 while the other options are susceptible to man-in-the-middle attacks. SSL
237 while the other options are susceptible to man-in-the-middle attacks. SSL
238 certificates can be installed to /etc/openldap/cacerts so that the
238 certificates can be installed to /etc/openldap/cacerts so that the
239 DEMAND or HARD options can be used with self-signed certificates or
239 DEMAND or HARD options can be used with self-signed certificates or
240 certificates that do not have traceable certificates of authority.
240 certificates that do not have traceable certificates of authority.
241
241
242 NEVER
242 NEVER
243 A serve certificate will never be requested or checked.
243 A serve certificate will never be requested or checked.
244
244
245 ALLOW
245 ALLOW
246 A server certificate is requested. Failure to provide a
246 A server certificate is requested. Failure to provide a
247 certificate or providing a bad certificate will not terminate the
247 certificate or providing a bad certificate will not terminate the
248 session.
248 session.
249
249
250 TRY
250 TRY
251 A server certificate is requested. Failure to provide a
251 A server certificate is requested. Failure to provide a
252 certificate does not halt the session; providing a bad certificate
252 certificate does not halt the session; providing a bad certificate
253 halts the session.
253 halts the session.
254
254
255 DEMAND
255 DEMAND
256 A server certificate is requested and must be provided and
256 A server certificate is requested and must be provided and
257 authenticated for the session to proceed.
257 authenticated for the session to proceed.
258
258
259 HARD
259 HARD
260 The same as DEMAND.
260 The same as DEMAND.
261
261
262 .. _Base DN:
262 .. _Base DN:
263
263
264 Base DN : required
264 Base DN : required
265 The Distinguished Name (DN) where searches for users will be performed.
265 The Distinguished Name (DN) where searches for users will be performed.
266 Searches can be controlled by `LDAP Filter`_ and `LDAP Search Scope`_.
266 Searches can be controlled by `LDAP Filter`_ and `LDAP Search Scope`_.
267
267
268 .. _LDAP Filter:
268 .. _LDAP Filter:
269
269
270 LDAP Filter : optional
270 LDAP Filter : optional
271 A LDAP filter defined by RFC 2254. This is more useful when `LDAP
271 A LDAP filter defined by RFC 2254. This is more useful when `LDAP
272 Search Scope`_ is set to SUBTREE. The filter is useful for limiting
272 Search Scope`_ is set to SUBTREE. The filter is useful for limiting
273 which LDAP objects are identified as representing Users for
273 which LDAP objects are identified as representing Users for
274 authentication. The filter is augmented by `Login Attribute`_ below.
274 authentication. The filter is augmented by `Login Attribute`_ below.
275 This can commonly be left blank.
275 This can commonly be left blank.
276
276
277 .. _LDAP Search Scope:
277 .. _LDAP Search Scope:
278
278
279 LDAP Search Scope : required
279 LDAP Search Scope : required
280 This limits how far LDAP will search for a matching object.
280 This limits how far LDAP will search for a matching object.
281
281
282 BASE
282 BASE
283 Only allows searching of `Base DN`_ and is usually not what you
283 Only allows searching of `Base DN`_ and is usually not what you
284 want.
284 want.
285
285
286 ONELEVEL
286 ONELEVEL
287 Searches all entries under `Base DN`_, but not Base DN itself.
287 Searches all entries under `Base DN`_, but not Base DN itself.
288
288
289 SUBTREE
289 SUBTREE
290 Searches all entries below `Base DN`_, but not Base DN itself.
290 Searches all entries below `Base DN`_, but not Base DN itself.
291 When using SUBTREE `LDAP Filter`_ is useful to limit object
291 When using SUBTREE `LDAP Filter`_ is useful to limit object
292 location.
292 location.
293
293
294 .. _Login Attribute:
294 .. _Login Attribute:
295
295
296 Login Attribute : required
296 Login Attribute : required
297 The LDAP record attribute that will be matched as the USERNAME or
297 The LDAP record attribute that will be matched as the USERNAME or
298 ACCOUNT used to connect to RhodeCode. This will be added to `LDAP
298 ACCOUNT used to connect to RhodeCode. This will be added to `LDAP
299 Filter`_ for locating the User object. If `LDAP Filter`_ is specified as
299 Filter`_ for locating the User object. If `LDAP Filter`_ is specified as
300 "LDAPFILTER", `Login Attribute`_ is specified as "uid" and the user has
300 "LDAPFILTER", `Login Attribute`_ is specified as "uid" and the user has
301 connected as "jsmith" then the `LDAP Filter`_ will be augmented as below
301 connected as "jsmith" then the `LDAP Filter`_ will be augmented as below
302 ::
302 ::
303
303
304 (&(LDAPFILTER)(uid=jsmith))
304 (&(LDAPFILTER)(uid=jsmith))
305
305
306 .. _ldap_attr_firstname:
306 .. _ldap_attr_firstname:
307
307
308 First Name Attribute : required
308 First Name Attribute : required
309 The LDAP record attribute which represents the user's first name.
309 The LDAP record attribute which represents the user's first name.
310
310
311 .. _ldap_attr_lastname:
311 .. _ldap_attr_lastname:
312
312
313 Last Name Attribute : required
313 Last Name Attribute : required
314 The LDAP record attribute which represents the user's last name.
314 The LDAP record attribute which represents the user's last name.
315
315
316 .. _ldap_attr_email:
316 .. _ldap_attr_email:
317
317
318 Email Attribute : required
318 Email Attribute : required
319 The LDAP record attribute which represents the user's email address.
319 The LDAP record attribute which represents the user's email address.
320
320
321 If all data are entered correctly, and python-ldap_ is properly installed
321 If all data are entered correctly, and python-ldap_ is properly installed
322 users should be granted access to RhodeCode with ldap accounts. At this
322 users should be granted access to RhodeCode with ldap accounts. At this
323 time user information is copied from LDAP into the RhodeCode user database.
323 time user information is copied from LDAP into the RhodeCode user database.
324 This means that updates of an LDAP user object may not be reflected as a
324 This means that updates of an LDAP user object may not be reflected as a
325 user update in RhodeCode.
325 user update in RhodeCode.
326
326
327 If You have problems with LDAP access and believe You entered correct
327 If You have problems with LDAP access and believe You entered correct
328 information check out the RhodeCode logs, any error messages sent from LDAP
328 information check out the RhodeCode logs, any error messages sent from LDAP
329 will be saved there.
329 will be saved there.
330
330
331 Active Directory
331 Active Directory
332 ''''''''''''''''
332 ''''''''''''''''
333
333
334 RhodeCode can use Microsoft Active Directory for user authentication. This
334 RhodeCode can use Microsoft Active Directory for user authentication. This
335 is done through an LDAP or LDAPS connection to Active Directory. The
335 is done through an LDAP or LDAPS connection to Active Directory. The
336 following LDAP configuration settings are typical for using Active
336 following LDAP configuration settings are typical for using Active
337 Directory ::
337 Directory ::
338
338
339 Base DN = OU=SBSUsers,OU=Users,OU=MyBusiness,DC=v3sys,DC=local
339 Base DN = OU=SBSUsers,OU=Users,OU=MyBusiness,DC=v3sys,DC=local
340 Login Attribute = sAMAccountName
340 Login Attribute = sAMAccountName
341 First Name Attribute = givenName
341 First Name Attribute = givenName
342 Last Name Attribute = sn
342 Last Name Attribute = sn
343 E-mail Attribute = mail
343 E-mail Attribute = mail
344
344
345 All other LDAP settings will likely be site-specific and should be
345 All other LDAP settings will likely be site-specific and should be
346 appropriately configured.
346 appropriately configured.
347
347
348
348
349
349
350 Hook management
350 Hook management
351 ---------------
351 ---------------
352
352
353 Hooks can be managed in similar way to this used in .hgrc files.
353 Hooks can be managed in similar way to this used in .hgrc files.
354 To access hooks setting click `advanced setup` on Hooks section of Mercurial
354 To access hooks setting click `advanced setup` on Hooks section of Mercurial
355 Settings in Admin.
355 Settings in Admin.
356
356
357 There are 4 built in hooks that cannot be changed (only enable/disable by
357 There are 4 built in hooks that cannot be changed (only enable/disable by
358 checkboxes on previos section).
358 checkboxes on previos section).
359 To add another custom hook simply fill in first section with
359 To add another custom hook simply fill in first section with
360 <name>.<hook_type> and the second one with hook path. Example hooks
360 <name>.<hook_type> and the second one with hook path. Example hooks
361 can be found at *rhodecode.lib.hooks*.
361 can be found at *rhodecode.lib.hooks*.
362
362
363
363
364 Setting Up Celery
364 Setting Up Celery
365 -----------------
365 -----------------
366
366
367 Since version 1.1 celery is configured by the rhodecode ini configuration files.
367 Since version 1.1 celery is configured by the rhodecode ini configuration files.
368 Simply set use_celery=true in the ini file then add / change the configuration
368 Simply set use_celery=true in the ini file then add / change the configuration
369 variables inside the ini file.
369 variables inside the ini file.
370
370
371 Remember that the ini files use the format with '.' not with '_' like celery.
371 Remember that the ini files use the format with '.' not with '_' like celery.
372 So for example setting `BROKER_HOST` in celery means setting `broker.host` in
372 So for example setting `BROKER_HOST` in celery means setting `broker.host` in
373 the config file.
373 the config file.
374
374
375 In order to start using celery run::
375 In order to start using celery run::
376
376
377 paster celeryd <configfile.ini>
377 paster celeryd <configfile.ini>
378
378
379
379
380 .. note::
380 .. note::
381 Make sure you run this command from the same virtualenv, and with the same
381 Make sure you run this command from the same virtualenv, and with the same
382 user that rhodecode runs.
382 user that rhodecode runs.
383
383
384 HTTPS support
384 HTTPS support
385 -------------
385 -------------
386
386
387 There are two ways to enable https:
387 There are two ways to enable https:
388
388
389 - Set HTTP_X_URL_SCHEME in your http server headers, than rhodecode will
389 - Set HTTP_X_URL_SCHEME in your http server headers, than rhodecode will
390 recognize this headers and make proper https redirections
390 recognize this headers and make proper https redirections
391 - Alternatively, change the `force_https = true` flag in the ini configuration
391 - Alternatively, change the `force_https = true` flag in the ini configuration
392 to force using https, no headers are needed than to enable https
392 to force using https, no headers are needed than to enable https
393
393
394
394
395 Nginx virtual host example
395 Nginx virtual host example
396 --------------------------
396 --------------------------
397
397
398 Sample config for nginx using proxy::
398 Sample config for nginx using proxy::
399
399
400 server {
400 server {
401 listen 80;
401 listen 80;
402 server_name hg.myserver.com;
402 server_name hg.myserver.com;
403 access_log /var/log/nginx/rhodecode.access.log;
403 access_log /var/log/nginx/rhodecode.access.log;
404 error_log /var/log/nginx/rhodecode.error.log;
404 error_log /var/log/nginx/rhodecode.error.log;
405 location / {
405 location / {
406 root /var/www/rhodecode/rhodecode/public/;
406 root /var/www/rhodecode/rhodecode/public/;
407 if (!-f $request_filename){
407 if (!-f $request_filename){
408 proxy_pass http://127.0.0.1:5000;
408 proxy_pass http://127.0.0.1:5000;
409 }
409 }
410 #this is important if you want to use https !!!
410 #this is important if you want to use https !!!
411 proxy_set_header X-Url-Scheme $scheme;
411 proxy_set_header X-Url-Scheme $scheme;
412 include /etc/nginx/proxy.conf;
412 include /etc/nginx/proxy.conf;
413 }
413 }
414 }
414 }
415
415
416 Here's the proxy.conf. It's tuned so it will not timeout on long
416 Here's the proxy.conf. It's tuned so it will not timeout on long
417 pushes or large pushes::
417 pushes or large pushes::
418
418
419 proxy_redirect off;
419 proxy_redirect off;
420 proxy_set_header Host $host;
420 proxy_set_header Host $host;
421 proxy_set_header X-Host $http_host;
421 proxy_set_header X-Host $http_host;
422 proxy_set_header X-Real-IP $remote_addr;
422 proxy_set_header X-Real-IP $remote_addr;
423 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
423 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
424 proxy_set_header Proxy-host $proxy_host;
424 proxy_set_header Proxy-host $proxy_host;
425 client_max_body_size 400m;
425 client_max_body_size 400m;
426 client_body_buffer_size 128k;
426 client_body_buffer_size 128k;
427 proxy_buffering off;
427 proxy_buffering off;
428 proxy_connect_timeout 7200;
428 proxy_connect_timeout 7200;
429 proxy_send_timeout 7200;
429 proxy_send_timeout 7200;
430 proxy_read_timeout 7200;
430 proxy_read_timeout 7200;
431 proxy_buffers 8 32k;
431 proxy_buffers 8 32k;
432
432
433 Also, when using root path with nginx you might set the static files to false
433 Also, when using root path with nginx you might set the static files to false
434 in the production.ini file::
434 in the production.ini file::
435
435
436 [app:main]
436 [app:main]
437 use = egg:rhodecode
437 use = egg:rhodecode
438 full_stack = true
438 full_stack = true
439 static_files = false
439 static_files = false
440 lang=en
440 lang=en
441 cache_dir = %(here)s/data
441 cache_dir = %(here)s/data
442
442
443 In order to not have the statics served by the application. This improves speed.
443 In order to not have the statics served by the application. This improves speed.
444
444
445
445
446 Apache virtual host example
446 Apache virtual host reverse proxy example
447 ---------------------------
447 -----------------------------------------
448
448
449 Here is a sample configuration file for apache using proxy::
449 Here is a sample configuration file for apache using proxy::
450
450
451 <VirtualHost *:80>
451 <VirtualHost *:80>
452 ServerName hg.myserver.com
452 ServerName hg.myserver.com
453 ServerAlias hg.myserver.com
453 ServerAlias hg.myserver.com
454
454
455 <Proxy *>
455 <Proxy *>
456 Order allow,deny
456 Order allow,deny
457 Allow from all
457 Allow from all
458 </Proxy>
458 </Proxy>
459
459
460 #important !
460 #important !
461 #Directive to properly generate url (clone url) for pylons
461 #Directive to properly generate url (clone url) for pylons
462 ProxyPreserveHost On
462 ProxyPreserveHost On
463
463
464 #rhodecode instance
464 #rhodecode instance
465 ProxyPass / http://127.0.0.1:5000/
465 ProxyPass / http://127.0.0.1:5000/
466 ProxyPassReverse / http://127.0.0.1:5000/
466 ProxyPassReverse / http://127.0.0.1:5000/
467
467
468 #to enable https use line below
468 #to enable https use line below
469 #SetEnvIf X-Url-Scheme https HTTPS=1
469 #SetEnvIf X-Url-Scheme https HTTPS=1
470
470
471 </VirtualHost>
471 </VirtualHost>
472
472
473
473
474 Additional tutorial
474 Additional tutorial
475 http://wiki.pylonshq.com/display/pylonscookbook/Apache+as+a+reverse+proxy+for+Pylons
475 http://wiki.pylonshq.com/display/pylonscookbook/Apache+as+a+reverse+proxy+for+Pylons
476
476
477
477
478 Apache as subdirectory
478 Apache as subdirectory
479 ----------------------
479 ----------------------
480
480
481 Apache subdirectory part::
481 Apache subdirectory part::
482
482
483 <Location /<someprefix> >
483 <Location /<someprefix> >
484 ProxyPass http://127.0.0.1:5000/<someprefix>
484 ProxyPass http://127.0.0.1:5000/<someprefix>
485 ProxyPassReverse http://127.0.0.1:5000/<someprefix>
485 ProxyPassReverse http://127.0.0.1:5000/<someprefix>
486 SetEnvIf X-Url-Scheme https HTTPS=1
486 SetEnvIf X-Url-Scheme https HTTPS=1
487 </Location>
487 </Location>
488
488
489 Besides the regular apache setup you will need to add the following line
489 Besides the regular apache setup you will need to add the following line
490 into [app:main] section of your .ini file::
490 into [app:main] section of your .ini file::
491
491
492 filter-with = proxy-prefix
492 filter-with = proxy-prefix
493
493
494 Add the following at the end of the .ini file::
494 Add the following at the end of the .ini file::
495
495
496 [filter:proxy-prefix]
496 [filter:proxy-prefix]
497 use = egg:PasteDeploy#prefix
497 use = egg:PasteDeploy#prefix
498 prefix = /<someprefix>
498 prefix = /<someprefix>
499
499
500
500
501 then change <someprefix> into your choosen prefix
501 then change <someprefix> into your choosen prefix
502
502
503 Apache's WSGI config
503 Apache's WSGI config
504 --------------------
504 --------------------
505
505
506 Alternatively, RhodeCode can be set up with Apache under mod_wsgi. For
507 that, you'll need to:
508
509 - Install mod_wsgi. If using a Debian-based distro, you can install
510 the package libapache2-mod-wsgi::
511
512 aptitude install libapache2-mod-wsgi
513
514 - Enable mod_wsgi::
515
516 a2enmod wsgi
517
518 - Create a wsgi dispatch script, like the one below. Make sure you
519 check the paths correctly point to where you installed RhodeCode
520 and its Python Virtual Environment.
521 - Enable the WSGIScriptAlias directive for the wsgi dispatch script,
522 as in the following example. Once again, check the paths are
523 correctly specified.
524
525 Here is a sample excerpt from an Apache Virtual Host configuration file::
526
527 WSGIDaemonProcess pylons user=www-data group=www-data processes=1 \
528 threads=4 \
529 python-path=/home/web/rhodecode/pyenv/lib/python2.6/site-packages
530 WSGIScriptAlias / /home/web/rhodecode/dispatch.wsgi
506
531
507 Example wsgi dispatch script::
532 Example wsgi dispatch script::
508
533
509 import os
534 import os
510 os.environ["HGENCODING"] = "UTF-8"
535 os.environ["HGENCODING"] = "UTF-8"
511 os.environ['PYTHON_EGG_CACHE'] = '/home/web/rhodecode/.egg-cache'
536 os.environ['PYTHON_EGG_CACHE'] = '/home/web/rhodecode/.egg-cache'
512
537
513 # sometimes it's needed to set the curent dir
538 # sometimes it's needed to set the curent dir
514 os.chdir('/home/web/rhodecode/')
539 os.chdir('/home/web/rhodecode/')
540
541 import site
542 site.addsitedir("/home/web/rhodecode/pyenv/lib/python2.6/site-packages")
515
543
516 from paste.deploy import loadapp
544 from paste.deploy import loadapp
517 from paste.script.util.logging_config import fileConfig
545 from paste.script.util.logging_config import fileConfig
518
546
519 fileConfig('/home/web/rhodecode/production.ini')
547 fileConfig('/home/web/rhodecode/production.ini')
520 application = loadapp('config:/home/web/rhodecode/production.ini')
548 application = loadapp('config:/home/web/rhodecode/production.ini')
521
549
550 Note: when using mod_wsgi you'll need to install the same version of
551 Mercurial that's inside RhodeCode's virtualenv also on the system's Python
552 environment.
553
522
554
523 Other configuration files
555 Other configuration files
524 -------------------------
556 -------------------------
525
557
526 Some example init.d scripts can be found here, for debian and gentoo:
558 Some example init.d scripts can be found here, for debian and gentoo:
527
559
528 https://rhodecode.org/rhodecode/files/tip/init.d
560 https://rhodecode.org/rhodecode/files/tip/init.d
529
561
530
562
531 Troubleshooting
563 Troubleshooting
532 ---------------
564 ---------------
533
565
534 :Q: **Missing static files?**
566 :Q: **Missing static files?**
535 :A: Make sure either to set the `static_files = true` in the .ini file or
567 :A: Make sure either to set the `static_files = true` in the .ini file or
536 double check the root path for your http setup. It should point to
568 double check the root path for your http setup. It should point to
537 for example:
569 for example:
538 /home/my-virtual-python/lib/python2.6/site-packages/rhodecode/public
570 /home/my-virtual-python/lib/python2.6/site-packages/rhodecode/public
539
571
540 |
572 |
541
573
542 :Q: **Can't install celery/rabbitmq**
574 :Q: **Can't install celery/rabbitmq**
543 :A: Don't worry RhodeCode works without them too. No extra setup is required.
575 :A: Don't worry RhodeCode works without them too. No extra setup is required.
544
576
545 |
577 |
546
578
547 :Q: **Long lasting push timeouts?**
579 :Q: **Long lasting push timeouts?**
548 :A: Make sure you set a longer timeouts in your proxy/fcgi settings, timeouts
580 :A: Make sure you set a longer timeouts in your proxy/fcgi settings, timeouts
549 are caused by https server and not RhodeCode.
581 are caused by https server and not RhodeCode.
550
582
551 |
583 |
552
584
553 :Q: **Large pushes timeouts?**
585 :Q: **Large pushes timeouts?**
554 :A: Make sure you set a proper max_body_size for the http server.
586 :A: Make sure you set a proper max_body_size for the http server.
555
587
556 |
588 |
557
589
558 :Q: **Apache doesn't pass basicAuth on pull/push?**
590 :Q: **Apache doesn't pass basicAuth on pull/push?**
559 :A: Make sure you added `WSGIPassAuthorization true`.
591 :A: Make sure you added `WSGIPassAuthorization true`.
560
592
561 For further questions search the `Issues tracker`_, or post a message in the
593 For further questions search the `Issues tracker`_, or post a message in the
562 `google group rhodecode`_
594 `google group rhodecode`_
563
595
564 .. _virtualenv: http://pypi.python.org/pypi/virtualenv
596 .. _virtualenv: http://pypi.python.org/pypi/virtualenv
565 .. _python: http://www.python.org/
597 .. _python: http://www.python.org/
566 .. _mercurial: http://mercurial.selenic.com/
598 .. _mercurial: http://mercurial.selenic.com/
567 .. _celery: http://celeryproject.org/
599 .. _celery: http://celeryproject.org/
568 .. _rabbitmq: http://www.rabbitmq.com/
600 .. _rabbitmq: http://www.rabbitmq.com/
569 .. _python-ldap: http://www.python-ldap.org/
601 .. _python-ldap: http://www.python-ldap.org/
570 .. _mercurial-server: http://www.lshift.net/mercurial-server.html
602 .. _mercurial-server: http://www.lshift.net/mercurial-server.html
571 .. _PublishingRepositories: http://mercurial.selenic.com/wiki/PublishingRepositories
603 .. _PublishingRepositories: http://mercurial.selenic.com/wiki/PublishingRepositories
572 .. _Issues tracker: https://bitbucket.org/marcinkuzminski/rhodecode/issues
604 .. _Issues tracker: https://bitbucket.org/marcinkuzminski/rhodecode/issues
573 .. _google group rhodecode: http://groups.google.com/group/rhodecode
605 .. _google group rhodecode: http://groups.google.com/group/rhodecode
@@ -1,56 +1,56
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.__init__
3 rhodecode.__init__
4 ~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~
5
5
6 RhodeCode, a web based repository management based on pylons
6 RhodeCode, a web based repository management based on pylons
7 versioning implementation: http://semver.org/
7 versioning implementation: http://semver.org/
8
8
9 :created_on: Apr 9, 2010
9 :created_on: Apr 9, 2010
10 :author: marcink
10 :author: marcink
11 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
12 :license: GPLv3, see COPYING for more details.
12 :license: GPLv3, see COPYING for more details.
13 """
13 """
14 # This program is free software: you can redistribute it and/or modify
14 # This program is free software: you can redistribute it and/or modify
15 # it under the terms of the GNU General Public License as published by
15 # it under the terms of the GNU General Public License as published by
16 # the Free Software Foundation, either version 3 of the License, or
16 # the Free Software Foundation, either version 3 of the License, or
17 # (at your option) any later version.
17 # (at your option) any later version.
18 #
18 #
19 # This program is distributed in the hope that it will be useful,
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # GNU General Public License for more details.
22 # GNU General Public License for more details.
23 #
23 #
24 # You should have received a copy of the GNU General Public License
24 # You should have received a copy of the GNU General Public License
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 import platform
26 import platform
27
27
28 VERSION = (1, 2, 1)
28 VERSION = (1, 2, 2)
29 __version__ = '.'.join((str(each) for each in VERSION[:4]))
29 __version__ = '.'.join((str(each) for each in VERSION[:4]))
30 __dbversion__ = 3 #defines current db version for migrations
30 __dbversion__ = 3 #defines current db version for migrations
31 __platform__ = platform.system()
31 __platform__ = platform.system()
32 __license__ = 'GPLv3'
32 __license__ = 'GPLv3'
33
33
34 PLATFORM_WIN = ('Windows')
34 PLATFORM_WIN = ('Windows')
35 PLATFORM_OTHERS = ('Linux', 'Darwin', 'FreeBSD', 'OpenBSD', 'SunOS')
35 PLATFORM_OTHERS = ('Linux', 'Darwin', 'FreeBSD', 'OpenBSD', 'SunOS')
36
36
37 try:
37 try:
38 from rhodecode.lib.utils import get_current_revision
38 from rhodecode.lib import get_current_revision
39 _rev = get_current_revision()
39 _rev = get_current_revision()
40 except ImportError:
40 except ImportError:
41 #this is needed when doing some setup.py operations
41 #this is needed when doing some setup.py operations
42 _rev = False
42 _rev = False
43
43
44 if len(VERSION) > 3 and _rev:
44 if len(VERSION) > 3 and _rev:
45 __version__ += ' [rev:%s]' % _rev[0]
45 __version__ += ' [rev:%s]' % _rev[0]
46
46
47
47
48 def get_version():
48 def get_version():
49 """Returns shorter version (digit parts only) as string."""
49 """Returns shorter version (digit parts only) as string."""
50
50
51 return '.'.join((str(each) for each in VERSION[:3]))
51 return '.'.join((str(each) for each in VERSION[:3]))
52
52
53 BACKENDS = {
53 BACKENDS = {
54 'hg': 'Mercurial repository',
54 'hg': 'Mercurial repository',
55 #'git': 'Git repository',
55 #'git': 'Git repository',
56 }
56 }
@@ -1,441 +1,441
1 """
1 """
2 Routes configuration
2 Routes configuration
3
3
4 The more specific and detailed routes should be defined first so they
4 The more specific and detailed routes should be defined first so they
5 may take precedent over the more generic routes. For more information
5 may take precedent over the more generic routes. For more information
6 refer to the routes manual at http://routes.groovie.org/docs/
6 refer to the routes manual at http://routes.groovie.org/docs/
7 """
7 """
8 from __future__ import with_statement
8 from __future__ import with_statement
9 from routes import Mapper
9 from routes import Mapper
10
10
11
11
12 # prefix for non repository related links needs to be prefixed with `/`
12 # prefix for non repository related links needs to be prefixed with `/`
13 ADMIN_PREFIX = '/_admin'
13 ADMIN_PREFIX = '/_admin'
14
14
15
15
16 def make_map(config):
16 def make_map(config):
17 """Create, configure and return the routes Mapper"""
17 """Create, configure and return the routes Mapper"""
18 rmap = Mapper(directory=config['pylons.paths']['controllers'],
18 rmap = Mapper(directory=config['pylons.paths']['controllers'],
19 always_scan=config['debug'])
19 always_scan=config['debug'])
20 rmap.minimization = False
20 rmap.minimization = False
21 rmap.explicit = False
21 rmap.explicit = False
22
22
23 from rhodecode.lib.utils import is_valid_repo
23 from rhodecode.lib.utils import is_valid_repo
24 from rhodecode.lib.utils import is_valid_repos_group
24 from rhodecode.lib.utils import is_valid_repos_group
25
25
26 def check_repo(environ, match_dict):
26 def check_repo(environ, match_dict):
27 """
27 """
28 check for valid repository for proper 404 handling
28 check for valid repository for proper 404 handling
29
29
30 :param environ:
30 :param environ:
31 :param match_dict:
31 :param match_dict:
32 """
32 """
33
33
34 repo_name = match_dict.get('repo_name')
34 repo_name = match_dict.get('repo_name')
35 return is_valid_repo(repo_name, config['base_path'])
35 return is_valid_repo(repo_name, config['base_path'])
36
36
37 def check_group(environ, match_dict):
37 def check_group(environ, match_dict):
38 """
38 """
39 check for valid repositories group for proper 404 handling
39 check for valid repositories group for proper 404 handling
40
40
41 :param environ:
41 :param environ:
42 :param match_dict:
42 :param match_dict:
43 """
43 """
44 repos_group_name = match_dict.get('group_name')
44 repos_group_name = match_dict.get('group_name')
45
45
46 return is_valid_repos_group(repos_group_name, config['base_path'])
46 return is_valid_repos_group(repos_group_name, config['base_path'])
47
47
48
48
49 def check_int(environ, match_dict):
49 def check_int(environ, match_dict):
50 return match_dict.get('id').isdigit()
50 return match_dict.get('id').isdigit()
51
51
52 # The ErrorController route (handles 404/500 error pages); it should
52 # The ErrorController route (handles 404/500 error pages); it should
53 # likely stay at the top, ensuring it can always be resolved
53 # likely stay at the top, ensuring it can always be resolved
54 rmap.connect('/error/{action}', controller='error')
54 rmap.connect('/error/{action}', controller='error')
55 rmap.connect('/error/{action}/{id}', controller='error')
55 rmap.connect('/error/{action}/{id}', controller='error')
56
56
57 #==========================================================================
57 #==========================================================================
58 # CUSTOM ROUTES HERE
58 # CUSTOM ROUTES HERE
59 #==========================================================================
59 #==========================================================================
60
60
61 #MAIN PAGE
61 #MAIN PAGE
62 rmap.connect('home', '/', controller='home', action='index')
62 rmap.connect('home', '/', controller='home', action='index')
63 rmap.connect('repo_switcher', '/repos', controller='home',
63 rmap.connect('repo_switcher', '/repos', controller='home',
64 action='repo_switcher')
64 action='repo_switcher')
65 rmap.connect('bugtracker',
65 rmap.connect('bugtracker',
66 "http://bitbucket.org/marcinkuzminski/rhodecode/issues",
66 "http://bitbucket.org/marcinkuzminski/rhodecode/issues",
67 _static=True)
67 _static=True)
68 rmap.connect('rhodecode_official', "http://rhodecode.org", _static=True)
68 rmap.connect('rhodecode_official', "http://rhodecode.org", _static=True)
69
69
70 #ADMIN REPOSITORY REST ROUTES
70 #ADMIN REPOSITORY REST ROUTES
71 with rmap.submapper(path_prefix=ADMIN_PREFIX,
71 with rmap.submapper(path_prefix=ADMIN_PREFIX,
72 controller='admin/repos') as m:
72 controller='admin/repos') as m:
73 m.connect("repos", "/repos",
73 m.connect("repos", "/repos",
74 action="create", conditions=dict(method=["POST"]))
74 action="create", conditions=dict(method=["POST"]))
75 m.connect("repos", "/repos",
75 m.connect("repos", "/repos",
76 action="index", conditions=dict(method=["GET"]))
76 action="index", conditions=dict(method=["GET"]))
77 m.connect("formatted_repos", "/repos.{format}",
77 m.connect("formatted_repos", "/repos.{format}",
78 action="index",
78 action="index",
79 conditions=dict(method=["GET"]))
79 conditions=dict(method=["GET"]))
80 m.connect("new_repo", "/repos/new",
80 m.connect("new_repo", "/repos/new",
81 action="new", conditions=dict(method=["GET"]))
81 action="new", conditions=dict(method=["GET"]))
82 m.connect("formatted_new_repo", "/repos/new.{format}",
82 m.connect("formatted_new_repo", "/repos/new.{format}",
83 action="new", conditions=dict(method=["GET"]))
83 action="new", conditions=dict(method=["GET"]))
84 m.connect("/repos/{repo_name:.*}",
84 m.connect("/repos/{repo_name:.*}",
85 action="update", conditions=dict(method=["PUT"],
85 action="update", conditions=dict(method=["PUT"],
86 function=check_repo))
86 function=check_repo))
87 m.connect("/repos/{repo_name:.*}",
87 m.connect("/repos/{repo_name:.*}",
88 action="delete", conditions=dict(method=["DELETE"],
88 action="delete", conditions=dict(method=["DELETE"],
89 function=check_repo))
89 function=check_repo))
90 m.connect("edit_repo", "/repos/{repo_name:.*}/edit",
90 m.connect("edit_repo", "/repos/{repo_name:.*}/edit",
91 action="edit", conditions=dict(method=["GET"],
91 action="edit", conditions=dict(method=["GET"],
92 function=check_repo))
92 function=check_repo))
93 m.connect("formatted_edit_repo", "/repos/{repo_name:.*}.{format}/edit",
93 m.connect("formatted_edit_repo", "/repos/{repo_name:.*}.{format}/edit",
94 action="edit", conditions=dict(method=["GET"],
94 action="edit", conditions=dict(method=["GET"],
95 function=check_repo))
95 function=check_repo))
96 m.connect("repo", "/repos/{repo_name:.*}",
96 m.connect("repo", "/repos/{repo_name:.*}",
97 action="show", conditions=dict(method=["GET"],
97 action="show", conditions=dict(method=["GET"],
98 function=check_repo))
98 function=check_repo))
99 m.connect("formatted_repo", "/repos/{repo_name:.*}.{format}",
99 m.connect("formatted_repo", "/repos/{repo_name:.*}.{format}",
100 action="show", conditions=dict(method=["GET"],
100 action="show", conditions=dict(method=["GET"],
101 function=check_repo))
101 function=check_repo))
102 #ajax delete repo perm user
102 #ajax delete repo perm user
103 m.connect('delete_repo_user', "/repos_delete_user/{repo_name:.*}",
103 m.connect('delete_repo_user', "/repos_delete_user/{repo_name:.*}",
104 action="delete_perm_user", conditions=dict(method=["DELETE"],
104 action="delete_perm_user", conditions=dict(method=["DELETE"],
105 function=check_repo))
105 function=check_repo))
106 #ajax delete repo perm users_group
106 #ajax delete repo perm users_group
107 m.connect('delete_repo_users_group',
107 m.connect('delete_repo_users_group',
108 "/repos_delete_users_group/{repo_name:.*}",
108 "/repos_delete_users_group/{repo_name:.*}",
109 action="delete_perm_users_group",
109 action="delete_perm_users_group",
110 conditions=dict(method=["DELETE"], function=check_repo))
110 conditions=dict(method=["DELETE"], function=check_repo))
111
111
112 #settings actions
112 #settings actions
113 m.connect('repo_stats', "/repos_stats/{repo_name:.*}",
113 m.connect('repo_stats', "/repos_stats/{repo_name:.*}",
114 action="repo_stats", conditions=dict(method=["DELETE"],
114 action="repo_stats", conditions=dict(method=["DELETE"],
115 function=check_repo))
115 function=check_repo))
116 m.connect('repo_cache', "/repos_cache/{repo_name:.*}",
116 m.connect('repo_cache', "/repos_cache/{repo_name:.*}",
117 action="repo_cache", conditions=dict(method=["DELETE"],
117 action="repo_cache", conditions=dict(method=["DELETE"],
118 function=check_repo))
118 function=check_repo))
119 m.connect('repo_public_journal',
119 m.connect('repo_public_journal',
120 "/repos_public_journal/{repo_name:.*}",
120 "/repos_public_journal/{repo_name:.*}",
121 action="repo_public_journal", conditions=dict(method=["PUT"],
121 action="repo_public_journal", conditions=dict(method=["PUT"],
122 function=check_repo))
122 function=check_repo))
123 m.connect('repo_pull', "/repo_pull/{repo_name:.*}",
123 m.connect('repo_pull', "/repo_pull/{repo_name:.*}",
124 action="repo_pull", conditions=dict(method=["PUT"],
124 action="repo_pull", conditions=dict(method=["PUT"],
125 function=check_repo))
125 function=check_repo))
126
126
127 with rmap.submapper(path_prefix=ADMIN_PREFIX,
127 with rmap.submapper(path_prefix=ADMIN_PREFIX,
128 controller='admin/repos_groups') as m:
128 controller='admin/repos_groups') as m:
129 m.connect("repos_groups", "/repos_groups",
129 m.connect("repos_groups", "/repos_groups",
130 action="create", conditions=dict(method=["POST"]))
130 action="create", conditions=dict(method=["POST"]))
131 m.connect("repos_groups", "/repos_groups",
131 m.connect("repos_groups", "/repos_groups",
132 action="index", conditions=dict(method=["GET"]))
132 action="index", conditions=dict(method=["GET"]))
133 m.connect("formatted_repos_groups", "/repos_groups.{format}",
133 m.connect("formatted_repos_groups", "/repos_groups.{format}",
134 action="index", conditions=dict(method=["GET"]))
134 action="index", conditions=dict(method=["GET"]))
135 m.connect("new_repos_group", "/repos_groups/new",
135 m.connect("new_repos_group", "/repos_groups/new",
136 action="new", conditions=dict(method=["GET"]))
136 action="new", conditions=dict(method=["GET"]))
137 m.connect("formatted_new_repos_group", "/repos_groups/new.{format}",
137 m.connect("formatted_new_repos_group", "/repos_groups/new.{format}",
138 action="new", conditions=dict(method=["GET"]))
138 action="new", conditions=dict(method=["GET"]))
139 m.connect("update_repos_group", "/repos_groups/{id}",
139 m.connect("update_repos_group", "/repos_groups/{id}",
140 action="update", conditions=dict(method=["PUT"],
140 action="update", conditions=dict(method=["PUT"],
141 function=check_int))
141 function=check_int))
142 m.connect("delete_repos_group", "/repos_groups/{id}",
142 m.connect("delete_repos_group", "/repos_groups/{id}",
143 action="delete", conditions=dict(method=["DELETE"],
143 action="delete", conditions=dict(method=["DELETE"],
144 function=check_int))
144 function=check_int))
145 m.connect("edit_repos_group", "/repos_groups/{id}/edit",
145 m.connect("edit_repos_group", "/repos_groups/{id}/edit",
146 action="edit", conditions=dict(method=["GET"],
146 action="edit", conditions=dict(method=["GET"],
147 function=check_int))
147 function=check_int))
148 m.connect("formatted_edit_repos_group",
148 m.connect("formatted_edit_repos_group",
149 "/repos_groups/{id}.{format}/edit",
149 "/repos_groups/{id}.{format}/edit",
150 action="edit", conditions=dict(method=["GET"],
150 action="edit", conditions=dict(method=["GET"],
151 function=check_int))
151 function=check_int))
152 m.connect("repos_group", "/repos_groups/{id}",
152 m.connect("repos_group", "/repos_groups/{id}",
153 action="show", conditions=dict(method=["GET"],
153 action="show", conditions=dict(method=["GET"],
154 function=check_int))
154 function=check_int))
155 m.connect("formatted_repos_group", "/repos_groups/{id}.{format}",
155 m.connect("formatted_repos_group", "/repos_groups/{id}.{format}",
156 action="show", conditions=dict(method=["GET"],
156 action="show", conditions=dict(method=["GET"],
157 function=check_int))
157 function=check_int))
158
158
159 #ADMIN USER REST ROUTES
159 #ADMIN USER REST ROUTES
160 with rmap.submapper(path_prefix=ADMIN_PREFIX,
160 with rmap.submapper(path_prefix=ADMIN_PREFIX,
161 controller='admin/users') as m:
161 controller='admin/users') as m:
162 m.connect("users", "/users",
162 m.connect("users", "/users",
163 action="create", conditions=dict(method=["POST"]))
163 action="create", conditions=dict(method=["POST"]))
164 m.connect("users", "/users",
164 m.connect("users", "/users",
165 action="index", conditions=dict(method=["GET"]))
165 action="index", conditions=dict(method=["GET"]))
166 m.connect("formatted_users", "/users.{format}",
166 m.connect("formatted_users", "/users.{format}",
167 action="index", conditions=dict(method=["GET"]))
167 action="index", conditions=dict(method=["GET"]))
168 m.connect("new_user", "/users/new",
168 m.connect("new_user", "/users/new",
169 action="new", conditions=dict(method=["GET"]))
169 action="new", conditions=dict(method=["GET"]))
170 m.connect("formatted_new_user", "/users/new.{format}",
170 m.connect("formatted_new_user", "/users/new.{format}",
171 action="new", conditions=dict(method=["GET"]))
171 action="new", conditions=dict(method=["GET"]))
172 m.connect("update_user", "/users/{id}",
172 m.connect("update_user", "/users/{id}",
173 action="update", conditions=dict(method=["PUT"]))
173 action="update", conditions=dict(method=["PUT"]))
174 m.connect("delete_user", "/users/{id}",
174 m.connect("delete_user", "/users/{id}",
175 action="delete", conditions=dict(method=["DELETE"]))
175 action="delete", conditions=dict(method=["DELETE"]))
176 m.connect("edit_user", "/users/{id}/edit",
176 m.connect("edit_user", "/users/{id}/edit",
177 action="edit", conditions=dict(method=["GET"]))
177 action="edit", conditions=dict(method=["GET"]))
178 m.connect("formatted_edit_user",
178 m.connect("formatted_edit_user",
179 "/users/{id}.{format}/edit",
179 "/users/{id}.{format}/edit",
180 action="edit", conditions=dict(method=["GET"]))
180 action="edit", conditions=dict(method=["GET"]))
181 m.connect("user", "/users/{id}",
181 m.connect("user", "/users/{id}",
182 action="show", conditions=dict(method=["GET"]))
182 action="show", conditions=dict(method=["GET"]))
183 m.connect("formatted_user", "/users/{id}.{format}",
183 m.connect("formatted_user", "/users/{id}.{format}",
184 action="show", conditions=dict(method=["GET"]))
184 action="show", conditions=dict(method=["GET"]))
185
185
186 #EXTRAS USER ROUTES
186 #EXTRAS USER ROUTES
187 m.connect("user_perm", "/users_perm/{id}",
187 m.connect("user_perm", "/users_perm/{id}",
188 action="update_perm", conditions=dict(method=["PUT"]))
188 action="update_perm", conditions=dict(method=["PUT"]))
189
189
190 #ADMIN USERS REST ROUTES
190 #ADMIN USERS REST ROUTES
191 with rmap.submapper(path_prefix=ADMIN_PREFIX,
191 with rmap.submapper(path_prefix=ADMIN_PREFIX,
192 controller='admin/users_groups') as m:
192 controller='admin/users_groups') as m:
193 m.connect("users_groups", "/users_groups",
193 m.connect("users_groups", "/users_groups",
194 action="create", conditions=dict(method=["POST"]))
194 action="create", conditions=dict(method=["POST"]))
195 m.connect("users_groups", "/users_groups",
195 m.connect("users_groups", "/users_groups",
196 action="index", conditions=dict(method=["GET"]))
196 action="index", conditions=dict(method=["GET"]))
197 m.connect("formatted_users_groups", "/users_groups.{format}",
197 m.connect("formatted_users_groups", "/users_groups.{format}",
198 action="index", conditions=dict(method=["GET"]))
198 action="index", conditions=dict(method=["GET"]))
199 m.connect("new_users_group", "/users_groups/new",
199 m.connect("new_users_group", "/users_groups/new",
200 action="new", conditions=dict(method=["GET"]))
200 action="new", conditions=dict(method=["GET"]))
201 m.connect("formatted_new_users_group", "/users_groups/new.{format}",
201 m.connect("formatted_new_users_group", "/users_groups/new.{format}",
202 action="new", conditions=dict(method=["GET"]))
202 action="new", conditions=dict(method=["GET"]))
203 m.connect("update_users_group", "/users_groups/{id}",
203 m.connect("update_users_group", "/users_groups/{id}",
204 action="update", conditions=dict(method=["PUT"]))
204 action="update", conditions=dict(method=["PUT"]))
205 m.connect("delete_users_group", "/users_groups/{id}",
205 m.connect("delete_users_group", "/users_groups/{id}",
206 action="delete", conditions=dict(method=["DELETE"]))
206 action="delete", conditions=dict(method=["DELETE"]))
207 m.connect("edit_users_group", "/users_groups/{id}/edit",
207 m.connect("edit_users_group", "/users_groups/{id}/edit",
208 action="edit", conditions=dict(method=["GET"]))
208 action="edit", conditions=dict(method=["GET"]))
209 m.connect("formatted_edit_users_group",
209 m.connect("formatted_edit_users_group",
210 "/users_groups/{id}.{format}/edit",
210 "/users_groups/{id}.{format}/edit",
211 action="edit", conditions=dict(method=["GET"]))
211 action="edit", conditions=dict(method=["GET"]))
212 m.connect("users_group", "/users_groups/{id}",
212 m.connect("users_group", "/users_groups/{id}",
213 action="show", conditions=dict(method=["GET"]))
213 action="show", conditions=dict(method=["GET"]))
214 m.connect("formatted_users_group", "/users_groups/{id}.{format}",
214 m.connect("formatted_users_group", "/users_groups/{id}.{format}",
215 action="show", conditions=dict(method=["GET"]))
215 action="show", conditions=dict(method=["GET"]))
216
216
217 #EXTRAS USER ROUTES
217 #EXTRAS USER ROUTES
218 m.connect("users_group_perm", "/users_groups_perm/{id}",
218 m.connect("users_group_perm", "/users_groups_perm/{id}",
219 action="update_perm", conditions=dict(method=["PUT"]))
219 action="update_perm", conditions=dict(method=["PUT"]))
220
220
221 #ADMIN GROUP REST ROUTES
221 #ADMIN GROUP REST ROUTES
222 rmap.resource('group', 'groups',
222 rmap.resource('group', 'groups',
223 controller='admin/groups', path_prefix=ADMIN_PREFIX)
223 controller='admin/groups', path_prefix=ADMIN_PREFIX)
224
224
225 #ADMIN PERMISSIONS REST ROUTES
225 #ADMIN PERMISSIONS REST ROUTES
226 rmap.resource('permission', 'permissions',
226 rmap.resource('permission', 'permissions',
227 controller='admin/permissions', path_prefix=ADMIN_PREFIX)
227 controller='admin/permissions', path_prefix=ADMIN_PREFIX)
228
228
229 ##ADMIN LDAP SETTINGS
229 ##ADMIN LDAP SETTINGS
230 rmap.connect('ldap_settings', '%s/ldap' % ADMIN_PREFIX,
230 rmap.connect('ldap_settings', '%s/ldap' % ADMIN_PREFIX,
231 controller='admin/ldap_settings', action='ldap_settings',
231 controller='admin/ldap_settings', action='ldap_settings',
232 conditions=dict(method=["POST"]))
232 conditions=dict(method=["POST"]))
233
233
234 rmap.connect('ldap_home', '%s/ldap' % ADMIN_PREFIX,
234 rmap.connect('ldap_home', '%s/ldap' % ADMIN_PREFIX,
235 controller='admin/ldap_settings')
235 controller='admin/ldap_settings')
236
236
237 #ADMIN SETTINGS REST ROUTES
237 #ADMIN SETTINGS REST ROUTES
238 with rmap.submapper(path_prefix=ADMIN_PREFIX,
238 with rmap.submapper(path_prefix=ADMIN_PREFIX,
239 controller='admin/settings') as m:
239 controller='admin/settings') as m:
240 m.connect("admin_settings", "/settings",
240 m.connect("admin_settings", "/settings",
241 action="create", conditions=dict(method=["POST"]))
241 action="create", conditions=dict(method=["POST"]))
242 m.connect("admin_settings", "/settings",
242 m.connect("admin_settings", "/settings",
243 action="index", conditions=dict(method=["GET"]))
243 action="index", conditions=dict(method=["GET"]))
244 m.connect("formatted_admin_settings", "/settings.{format}",
244 m.connect("formatted_admin_settings", "/settings.{format}",
245 action="index", conditions=dict(method=["GET"]))
245 action="index", conditions=dict(method=["GET"]))
246 m.connect("admin_new_setting", "/settings/new",
246 m.connect("admin_new_setting", "/settings/new",
247 action="new", conditions=dict(method=["GET"]))
247 action="new", conditions=dict(method=["GET"]))
248 m.connect("formatted_admin_new_setting", "/settings/new.{format}",
248 m.connect("formatted_admin_new_setting", "/settings/new.{format}",
249 action="new", conditions=dict(method=["GET"]))
249 action="new", conditions=dict(method=["GET"]))
250 m.connect("/settings/{setting_id}",
250 m.connect("/settings/{setting_id}",
251 action="update", conditions=dict(method=["PUT"]))
251 action="update", conditions=dict(method=["PUT"]))
252 m.connect("/settings/{setting_id}",
252 m.connect("/settings/{setting_id}",
253 action="delete", conditions=dict(method=["DELETE"]))
253 action="delete", conditions=dict(method=["DELETE"]))
254 m.connect("admin_edit_setting", "/settings/{setting_id}/edit",
254 m.connect("admin_edit_setting", "/settings/{setting_id}/edit",
255 action="edit", conditions=dict(method=["GET"]))
255 action="edit", conditions=dict(method=["GET"]))
256 m.connect("formatted_admin_edit_setting",
256 m.connect("formatted_admin_edit_setting",
257 "/settings/{setting_id}.{format}/edit",
257 "/settings/{setting_id}.{format}/edit",
258 action="edit", conditions=dict(method=["GET"]))
258 action="edit", conditions=dict(method=["GET"]))
259 m.connect("admin_setting", "/settings/{setting_id}",
259 m.connect("admin_setting", "/settings/{setting_id}",
260 action="show", conditions=dict(method=["GET"]))
260 action="show", conditions=dict(method=["GET"]))
261 m.connect("formatted_admin_setting", "/settings/{setting_id}.{format}",
261 m.connect("formatted_admin_setting", "/settings/{setting_id}.{format}",
262 action="show", conditions=dict(method=["GET"]))
262 action="show", conditions=dict(method=["GET"]))
263 m.connect("admin_settings_my_account", "/my_account",
263 m.connect("admin_settings_my_account", "/my_account",
264 action="my_account", conditions=dict(method=["GET"]))
264 action="my_account", conditions=dict(method=["GET"]))
265 m.connect("admin_settings_my_account_update", "/my_account_update",
265 m.connect("admin_settings_my_account_update", "/my_account_update",
266 action="my_account_update", conditions=dict(method=["PUT"]))
266 action="my_account_update", conditions=dict(method=["PUT"]))
267 m.connect("admin_settings_create_repository", "/create_repository",
267 m.connect("admin_settings_create_repository", "/create_repository",
268 action="create_repository", conditions=dict(method=["GET"]))
268 action="create_repository", conditions=dict(method=["GET"]))
269
269
270
270
271 #ADMIN MAIN PAGES
271 #ADMIN MAIN PAGES
272 with rmap.submapper(path_prefix=ADMIN_PREFIX,
272 with rmap.submapper(path_prefix=ADMIN_PREFIX,
273 controller='admin/admin') as m:
273 controller='admin/admin') as m:
274 m.connect('admin_home', '', action='index')
274 m.connect('admin_home', '', action='index')
275 m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
275 m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
276 action='add_repo')
276 action='add_repo')
277
277
278 #==========================================================================
278 #==========================================================================
279 # API V1
279 # API V1
280 #==========================================================================
280 #==========================================================================
281 with rmap.submapper(path_prefix=ADMIN_PREFIX,
281 with rmap.submapper(path_prefix=ADMIN_PREFIX,
282 controller='api/api') as m:
282 controller='api/api') as m:
283 m.connect('api', '/api')
283 m.connect('api', '/api')
284
284
285
285
286 #USER JOURNAL
286 #USER JOURNAL
287 rmap.connect('journal', '%s/journal' % ADMIN_PREFIX, controller='journal')
287 rmap.connect('journal', '%s/journal' % ADMIN_PREFIX, controller='journal')
288
288
289 rmap.connect('public_journal', '%s/public_journal' % ADMIN_PREFIX,
289 rmap.connect('public_journal', '%s/public_journal' % ADMIN_PREFIX,
290 controller='journal', action="public_journal")
290 controller='journal', action="public_journal")
291
291
292 rmap.connect('public_journal_rss', '%s/public_journal_rss' % ADMIN_PREFIX,
292 rmap.connect('public_journal_rss', '%s/public_journal_rss' % ADMIN_PREFIX,
293 controller='journal', action="public_journal_rss")
293 controller='journal', action="public_journal_rss")
294
294
295 rmap.connect('public_journal_atom',
295 rmap.connect('public_journal_atom',
296 '%s/public_journal_atom' % ADMIN_PREFIX, controller='journal',
296 '%s/public_journal_atom' % ADMIN_PREFIX, controller='journal',
297 action="public_journal_atom")
297 action="public_journal_atom")
298
298
299 rmap.connect('toggle_following', '%s/toggle_following' % ADMIN_PREFIX,
299 rmap.connect('toggle_following', '%s/toggle_following' % ADMIN_PREFIX,
300 controller='journal', action='toggle_following',
300 controller='journal', action='toggle_following',
301 conditions=dict(method=["POST"]))
301 conditions=dict(method=["POST"]))
302
302
303 #SEARCH
303 #SEARCH
304 rmap.connect('search', '%s/search' % ADMIN_PREFIX, controller='search',)
304 rmap.connect('search', '%s/search' % ADMIN_PREFIX, controller='search',)
305 rmap.connect('search_repo', '%s/search/{search_repo:.*}' % ADMIN_PREFIX,
305 rmap.connect('search_repo', '%s/search/{search_repo:.*}' % ADMIN_PREFIX,
306 controller='search')
306 controller='search')
307
307
308 #LOGIN/LOGOUT/REGISTER/SIGN IN
308 #LOGIN/LOGOUT/REGISTER/SIGN IN
309 rmap.connect('login_home', '%s/login' % ADMIN_PREFIX, controller='login')
309 rmap.connect('login_home', '%s/login' % ADMIN_PREFIX, controller='login')
310 rmap.connect('logout_home', '%s/logout' % ADMIN_PREFIX, controller='login',
310 rmap.connect('logout_home', '%s/logout' % ADMIN_PREFIX, controller='login',
311 action='logout')
311 action='logout')
312
312
313 rmap.connect('register', '%s/register' % ADMIN_PREFIX, controller='login',
313 rmap.connect('register', '%s/register' % ADMIN_PREFIX, controller='login',
314 action='register')
314 action='register')
315
315
316 rmap.connect('reset_password', '%s/password_reset' % ADMIN_PREFIX,
316 rmap.connect('reset_password', '%s/password_reset' % ADMIN_PREFIX,
317 controller='login', action='password_reset')
317 controller='login', action='password_reset')
318
318
319 rmap.connect('reset_password_confirmation',
319 rmap.connect('reset_password_confirmation',
320 '%s/password_reset_confirmation' % ADMIN_PREFIX,
320 '%s/password_reset_confirmation' % ADMIN_PREFIX,
321 controller='login', action='password_reset_confirmation')
321 controller='login', action='password_reset_confirmation')
322
322
323 #FEEDS
323 #FEEDS
324 rmap.connect('rss_feed_home', '/{repo_name:.*}/feed/rss',
324 rmap.connect('rss_feed_home', '/{repo_name:.*}/feed/rss',
325 controller='feed', action='rss',
325 controller='feed', action='rss',
326 conditions=dict(function=check_repo))
326 conditions=dict(function=check_repo))
327
327
328 rmap.connect('atom_feed_home', '/{repo_name:.*}/feed/atom',
328 rmap.connect('atom_feed_home', '/{repo_name:.*}/feed/atom',
329 controller='feed', action='atom',
329 controller='feed', action='atom',
330 conditions=dict(function=check_repo))
330 conditions=dict(function=check_repo))
331
331
332 #==========================================================================
332 #==========================================================================
333 # REPOSITORY ROUTES
333 # REPOSITORY ROUTES
334 #==========================================================================
334 #==========================================================================
335 rmap.connect('summary_home', '/{repo_name:.*}',
335 rmap.connect('summary_home', '/{repo_name:.*}',
336 controller='summary',
336 controller='summary',
337 conditions=dict(function=check_repo))
337 conditions=dict(function=check_repo))
338
338
339 # rmap.connect('repo_group_home', '/{group_name:.*}',
339 rmap.connect('repos_group_home', '/{group_name:.*}',
340 # controller='admin/repos_groups',action="show_by_name",
340 controller='admin/repos_groups', action="show_by_name",
341 # conditions=dict(function=check_group))
341 conditions=dict(function=check_group))
342
342
343 rmap.connect('changeset_home', '/{repo_name:.*}/changeset/{revision}',
343 rmap.connect('changeset_home', '/{repo_name:.*}/changeset/{revision}',
344 controller='changeset', revision='tip',
344 controller='changeset', revision='tip',
345 conditions=dict(function=check_repo))
345 conditions=dict(function=check_repo))
346
346
347 rmap.connect('raw_changeset_home',
347 rmap.connect('raw_changeset_home',
348 '/{repo_name:.*}/raw-changeset/{revision}',
348 '/{repo_name:.*}/raw-changeset/{revision}',
349 controller='changeset', action='raw_changeset',
349 controller='changeset', action='raw_changeset',
350 revision='tip', conditions=dict(function=check_repo))
350 revision='tip', conditions=dict(function=check_repo))
351
351
352 rmap.connect('summary_home', '/{repo_name:.*}/summary',
352 rmap.connect('summary_home', '/{repo_name:.*}/summary',
353 controller='summary', conditions=dict(function=check_repo))
353 controller='summary', conditions=dict(function=check_repo))
354
354
355 rmap.connect('shortlog_home', '/{repo_name:.*}/shortlog',
355 rmap.connect('shortlog_home', '/{repo_name:.*}/shortlog',
356 controller='shortlog', conditions=dict(function=check_repo))
356 controller='shortlog', conditions=dict(function=check_repo))
357
357
358 rmap.connect('branches_home', '/{repo_name:.*}/branches',
358 rmap.connect('branches_home', '/{repo_name:.*}/branches',
359 controller='branches', conditions=dict(function=check_repo))
359 controller='branches', conditions=dict(function=check_repo))
360
360
361 rmap.connect('tags_home', '/{repo_name:.*}/tags',
361 rmap.connect('tags_home', '/{repo_name:.*}/tags',
362 controller='tags', conditions=dict(function=check_repo))
362 controller='tags', conditions=dict(function=check_repo))
363
363
364 rmap.connect('changelog_home', '/{repo_name:.*}/changelog',
364 rmap.connect('changelog_home', '/{repo_name:.*}/changelog',
365 controller='changelog', conditions=dict(function=check_repo))
365 controller='changelog', conditions=dict(function=check_repo))
366
366
367 rmap.connect('changelog_details', '/{repo_name:.*}/changelog_details/{cs}',
367 rmap.connect('changelog_details', '/{repo_name:.*}/changelog_details/{cs}',
368 controller='changelog', action='changelog_details',
368 controller='changelog', action='changelog_details',
369 conditions=dict(function=check_repo))
369 conditions=dict(function=check_repo))
370
370
371 rmap.connect('files_home', '/{repo_name:.*}/files/{revision}/{f_path:.*}',
371 rmap.connect('files_home', '/{repo_name:.*}/files/{revision}/{f_path:.*}',
372 controller='files', revision='tip', f_path='',
372 controller='files', revision='tip', f_path='',
373 conditions=dict(function=check_repo))
373 conditions=dict(function=check_repo))
374
374
375 rmap.connect('files_diff_home', '/{repo_name:.*}/diff/{f_path:.*}',
375 rmap.connect('files_diff_home', '/{repo_name:.*}/diff/{f_path:.*}',
376 controller='files', action='diff', revision='tip', f_path='',
376 controller='files', action='diff', revision='tip', f_path='',
377 conditions=dict(function=check_repo))
377 conditions=dict(function=check_repo))
378
378
379 rmap.connect('files_rawfile_home',
379 rmap.connect('files_rawfile_home',
380 '/{repo_name:.*}/rawfile/{revision}/{f_path:.*}',
380 '/{repo_name:.*}/rawfile/{revision}/{f_path:.*}',
381 controller='files', action='rawfile', revision='tip',
381 controller='files', action='rawfile', revision='tip',
382 f_path='', conditions=dict(function=check_repo))
382 f_path='', conditions=dict(function=check_repo))
383
383
384 rmap.connect('files_raw_home',
384 rmap.connect('files_raw_home',
385 '/{repo_name:.*}/raw/{revision}/{f_path:.*}',
385 '/{repo_name:.*}/raw/{revision}/{f_path:.*}',
386 controller='files', action='raw', revision='tip', f_path='',
386 controller='files', action='raw', revision='tip', f_path='',
387 conditions=dict(function=check_repo))
387 conditions=dict(function=check_repo))
388
388
389 rmap.connect('files_annotate_home',
389 rmap.connect('files_annotate_home',
390 '/{repo_name:.*}/annotate/{revision}/{f_path:.*}',
390 '/{repo_name:.*}/annotate/{revision}/{f_path:.*}',
391 controller='files', action='annotate', revision='tip',
391 controller='files', action='annotate', revision='tip',
392 f_path='', conditions=dict(function=check_repo))
392 f_path='', conditions=dict(function=check_repo))
393
393
394 rmap.connect('files_edit_home',
394 rmap.connect('files_edit_home',
395 '/{repo_name:.*}/edit/{revision}/{f_path:.*}',
395 '/{repo_name:.*}/edit/{revision}/{f_path:.*}',
396 controller='files', action='edit', revision='tip',
396 controller='files', action='edit', revision='tip',
397 f_path='', conditions=dict(function=check_repo))
397 f_path='', conditions=dict(function=check_repo))
398
398
399 rmap.connect('files_add_home',
399 rmap.connect('files_add_home',
400 '/{repo_name:.*}/add/{revision}/{f_path:.*}',
400 '/{repo_name:.*}/add/{revision}/{f_path:.*}',
401 controller='files', action='add', revision='tip',
401 controller='files', action='add', revision='tip',
402 f_path='', conditions=dict(function=check_repo))
402 f_path='', conditions=dict(function=check_repo))
403
403
404 rmap.connect('files_archive_home', '/{repo_name:.*}/archive/{fname}',
404 rmap.connect('files_archive_home', '/{repo_name:.*}/archive/{fname}',
405 controller='files', action='archivefile',
405 controller='files', action='archivefile',
406 conditions=dict(function=check_repo))
406 conditions=dict(function=check_repo))
407
407
408 rmap.connect('files_nodelist_home',
408 rmap.connect('files_nodelist_home',
409 '/{repo_name:.*}/nodelist/{revision}/{f_path:.*}',
409 '/{repo_name:.*}/nodelist/{revision}/{f_path:.*}',
410 controller='files', action='nodelist',
410 controller='files', action='nodelist',
411 conditions=dict(function=check_repo))
411 conditions=dict(function=check_repo))
412
412
413 rmap.connect('repo_settings_delete', '/{repo_name:.*}/settings',
413 rmap.connect('repo_settings_delete', '/{repo_name:.*}/settings',
414 controller='settings', action="delete",
414 controller='settings', action="delete",
415 conditions=dict(method=["DELETE"], function=check_repo))
415 conditions=dict(method=["DELETE"], function=check_repo))
416
416
417 rmap.connect('repo_settings_update', '/{repo_name:.*}/settings',
417 rmap.connect('repo_settings_update', '/{repo_name:.*}/settings',
418 controller='settings', action="update",
418 controller='settings', action="update",
419 conditions=dict(method=["PUT"], function=check_repo))
419 conditions=dict(method=["PUT"], function=check_repo))
420
420
421 rmap.connect('repo_settings_home', '/{repo_name:.*}/settings',
421 rmap.connect('repo_settings_home', '/{repo_name:.*}/settings',
422 controller='settings', action='index',
422 controller='settings', action='index',
423 conditions=dict(function=check_repo))
423 conditions=dict(function=check_repo))
424
424
425 rmap.connect('repo_fork_create_home', '/{repo_name:.*}/fork',
425 rmap.connect('repo_fork_create_home', '/{repo_name:.*}/fork',
426 controller='settings', action='fork_create',
426 controller='settings', action='fork_create',
427 conditions=dict(function=check_repo, method=["POST"]))
427 conditions=dict(function=check_repo, method=["POST"]))
428
428
429 rmap.connect('repo_fork_home', '/{repo_name:.*}/fork',
429 rmap.connect('repo_fork_home', '/{repo_name:.*}/fork',
430 controller='settings', action='fork',
430 controller='settings', action='fork',
431 conditions=dict(function=check_repo))
431 conditions=dict(function=check_repo))
432
432
433 rmap.connect('repo_followers_home', '/{repo_name:.*}/followers',
433 rmap.connect('repo_followers_home', '/{repo_name:.*}/followers',
434 controller='followers', action='followers',
434 controller='followers', action='followers',
435 conditions=dict(function=check_repo))
435 conditions=dict(function=check_repo))
436
436
437 rmap.connect('repo_forks_home', '/{repo_name:.*}/forks',
437 rmap.connect('repo_forks_home', '/{repo_name:.*}/forks',
438 controller='forks', action='forks',
438 controller='forks', action='forks',
439 conditions=dict(function=check_repo))
439 conditions=dict(function=check_repo))
440
440
441 return rmap
441 return rmap
@@ -1,167 +1,167
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.admin.permissions
3 rhodecode.controllers.admin.permissions
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 permissions controller for Rhodecode
6 permissions controller for Rhodecode
7
7
8 :created_on: Apr 27, 2010
8 :created_on: Apr 27, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import logging
26 import logging
27 import traceback
27 import traceback
28 import formencode
28 import formencode
29 from formencode import htmlfill
29 from formencode import htmlfill
30
30
31 from pylons import request, session, tmpl_context as c, url
31 from pylons import request, session, tmpl_context as c, url
32 from pylons.controllers.util import abort, redirect
32 from pylons.controllers.util import abort, redirect
33 from pylons.i18n.translation import _
33 from pylons.i18n.translation import _
34
34
35 from rhodecode.lib import helpers as h
35 from rhodecode.lib import helpers as h
36 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
36 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
37 from rhodecode.lib.base import BaseController, render
37 from rhodecode.lib.base import BaseController, render
38 from rhodecode.model.forms import DefaultPermissionsForm
38 from rhodecode.model.forms import DefaultPermissionsForm
39 from rhodecode.model.permission import PermissionModel
39 from rhodecode.model.permission import PermissionModel
40 from rhodecode.model.db import User
40 from rhodecode.model.db import User
41
41
42 log = logging.getLogger(__name__)
42 log = logging.getLogger(__name__)
43
43
44
44
45 class PermissionsController(BaseController):
45 class PermissionsController(BaseController):
46 """REST Controller styled on the Atom Publishing Protocol"""
46 """REST Controller styled on the Atom Publishing Protocol"""
47 # To properly map this controller, ensure your config/routing.py
47 # To properly map this controller, ensure your config/routing.py
48 # file has a resource setup:
48 # file has a resource setup:
49 # map.resource('permission', 'permissions')
49 # map.resource('permission', 'permissions')
50
50
51 @LoginRequired()
51 @LoginRequired()
52 @HasPermissionAllDecorator('hg.admin')
52 @HasPermissionAllDecorator('hg.admin')
53 def __before__(self):
53 def __before__(self):
54 c.admin_user = session.get('admin_user')
54 c.admin_user = session.get('admin_user')
55 c.admin_username = session.get('admin_username')
55 c.admin_username = session.get('admin_username')
56 super(PermissionsController, self).__before__()
56 super(PermissionsController, self).__before__()
57
57
58 self.perms_choices = [('repository.none', _('None'),),
58 self.perms_choices = [('repository.none', _('None'),),
59 ('repository.read', _('Read'),),
59 ('repository.read', _('Read'),),
60 ('repository.write', _('Write'),),
60 ('repository.write', _('Write'),),
61 ('repository.admin', _('Admin'),)]
61 ('repository.admin', _('Admin'),)]
62 self.register_choices = [
62 self.register_choices = [
63 ('hg.register.none',
63 ('hg.register.none',
64 _('disabled')),
64 _('disabled')),
65 ('hg.register.manual_activate',
65 ('hg.register.manual_activate',
66 _('allowed with manual account activation')),
66 _('allowed with manual account activation')),
67 ('hg.register.auto_activate',
67 ('hg.register.auto_activate',
68 _('allowed with automatic account activation')), ]
68 _('allowed with automatic account activation')), ]
69
69
70 self.create_choices = [('hg.create.none', _('Disabled')),
70 self.create_choices = [('hg.create.none', _('Disabled')),
71 ('hg.create.repository', _('Enabled'))]
71 ('hg.create.repository', _('Enabled'))]
72
72
73 def index(self, format='html'):
73 def index(self, format='html'):
74 """GET /permissions: All items in the collection"""
74 """GET /permissions: All items in the collection"""
75 # url('permissions')
75 # url('permissions')
76
76
77 def create(self):
77 def create(self):
78 """POST /permissions: Create a new item"""
78 """POST /permissions: Create a new item"""
79 # url('permissions')
79 # url('permissions')
80
80
81 def new(self, format='html'):
81 def new(self, format='html'):
82 """GET /permissions/new: Form to create a new item"""
82 """GET /permissions/new: Form to create a new item"""
83 # url('new_permission')
83 # url('new_permission')
84
84
85 def update(self, id):
85 def update(self, id):
86 """PUT /permissions/id: Update an existing item"""
86 """PUT /permissions/id: Update an existing item"""
87 # Forms posted to this method should contain a hidden field:
87 # Forms posted to this method should contain a hidden field:
88 # <input type="hidden" name="_method" value="PUT" />
88 # <input type="hidden" name="_method" value="PUT" />
89 # Or using helpers:
89 # Or using helpers:
90 # h.form(url('permission', id=ID),
90 # h.form(url('permission', id=ID),
91 # method='put')
91 # method='put')
92 # url('permission', id=ID)
92 # url('permission', id=ID)
93
93
94 permission_model = PermissionModel()
94 permission_model = PermissionModel()
95
95
96 _form = DefaultPermissionsForm([x[0] for x in self.perms_choices],
96 _form = DefaultPermissionsForm([x[0] for x in self.perms_choices],
97 [x[0] for x in self.register_choices],
97 [x[0] for x in self.register_choices],
98 [x[0] for x in self.create_choices])()
98 [x[0] for x in self.create_choices])()
99
99
100 try:
100 try:
101 form_result = _form.to_python(dict(request.POST))
101 form_result = _form.to_python(dict(request.POST))
102 form_result.update({'perm_user_name': id})
102 form_result.update({'perm_user_name': id})
103 permission_model.update(form_result)
103 permission_model.update(form_result)
104 h.flash(_('Default permissions updated successfully'),
104 h.flash(_('Default permissions updated successfully'),
105 category='success')
105 category='success')
106
106
107 except formencode.Invalid, errors:
107 except formencode.Invalid, errors:
108 c.perms_choices = self.perms_choices
108 c.perms_choices = self.perms_choices
109 c.register_choices = self.register_choices
109 c.register_choices = self.register_choices
110 c.create_choices = self.create_choices
110 c.create_choices = self.create_choices
111 defaults = errors.value
111 defaults = errors.value
112
112
113 return htmlfill.render(
113 return htmlfill.render(
114 render('admin/permissions/permissions.html'),
114 render('admin/permissions/permissions.html'),
115 defaults=defaults,
115 defaults=defaults,
116 errors=errors.error_dict or {},
116 errors=errors.error_dict or {},
117 prefix_error=False,
117 prefix_error=False,
118 encoding="UTF-8")
118 encoding="UTF-8")
119 except Exception:
119 except Exception:
120 log.error(traceback.format_exc())
120 log.error(traceback.format_exc())
121 h.flash(_('error occurred during update of permissions'),
121 h.flash(_('error occurred during update of permissions'),
122 category='error')
122 category='error')
123
123
124 return redirect(url('edit_permission', id=id))
124 return redirect(url('edit_permission', id=id))
125
125
126 def delete(self, id):
126 def delete(self, id):
127 """DELETE /permissions/id: Delete an existing item"""
127 """DELETE /permissions/id: Delete an existing item"""
128 # Forms posted to this method should contain a hidden field:
128 # Forms posted to this method should contain a hidden field:
129 # <input type="hidden" name="_method" value="DELETE" />
129 # <input type="hidden" name="_method" value="DELETE" />
130 # Or using helpers:
130 # Or using helpers:
131 # h.form(url('permission', id=ID),
131 # h.form(url('permission', id=ID),
132 # method='delete')
132 # method='delete')
133 # url('permission', id=ID)
133 # url('permission', id=ID)
134
134
135 def show(self, id, format='html'):
135 def show(self, id, format='html'):
136 """GET /permissions/id: Show a specific item"""
136 """GET /permissions/id: Show a specific item"""
137 # url('permission', id=ID)
137 # url('permission', id=ID)
138
138
139 def edit(self, id, format='html'):
139 def edit(self, id, format='html'):
140 """GET /permissions/id/edit: Form to edit an existing item"""
140 """GET /permissions/id/edit: Form to edit an existing item"""
141 #url('edit_permission', id=ID)
141 #url('edit_permission', id=ID)
142 c.perms_choices = self.perms_choices
142 c.perms_choices = self.perms_choices
143 c.register_choices = self.register_choices
143 c.register_choices = self.register_choices
144 c.create_choices = self.create_choices
144 c.create_choices = self.create_choices
145
145
146 if id == 'default':
146 if id == 'default':
147 default_user = User.by_username('default')
147 default_user = User.get_by_username('default')
148 defaults = {'_method': 'put',
148 defaults = {'_method': 'put',
149 'anonymous': default_user.active}
149 'anonymous': default_user.active}
150
150
151 for p in default_user.user_perms:
151 for p in default_user.user_perms:
152 if p.permission.permission_name.startswith('repository.'):
152 if p.permission.permission_name.startswith('repository.'):
153 defaults['default_perm'] = p.permission.permission_name
153 defaults['default_perm'] = p.permission.permission_name
154
154
155 if p.permission.permission_name.startswith('hg.register.'):
155 if p.permission.permission_name.startswith('hg.register.'):
156 defaults['default_register'] = p.permission.permission_name
156 defaults['default_register'] = p.permission.permission_name
157
157
158 if p.permission.permission_name.startswith('hg.create.'):
158 if p.permission.permission_name.startswith('hg.create.'):
159 defaults['default_create'] = p.permission.permission_name
159 defaults['default_create'] = p.permission.permission_name
160
160
161 return htmlfill.render(
161 return htmlfill.render(
162 render('admin/permissions/permissions.html'),
162 render('admin/permissions/permissions.html'),
163 defaults=defaults,
163 defaults=defaults,
164 encoding="UTF-8",
164 encoding="UTF-8",
165 force_defaults=True,)
165 force_defaults=True,)
166 else:
166 else:
167 return redirect(url('admin_home'))
167 return redirect(url('admin_home'))
@@ -1,431 +1,421
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.admin.repos
3 rhodecode.controllers.admin.repos
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Admin controller for RhodeCode
6 Admin controller for RhodeCode
7
7
8 :created_on: Apr 7, 2010
8 :created_on: Apr 7, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import logging
26 import logging
27 import traceback
27 import traceback
28 import formencode
28 import formencode
29 from operator import itemgetter
29 from operator import itemgetter
30 from formencode import htmlfill
30 from formencode import htmlfill
31
31
32 from paste.httpexceptions import HTTPInternalServerError
32 from paste.httpexceptions import HTTPInternalServerError
33 from pylons import request, response, session, tmpl_context as c, url
33 from pylons import request, response, session, tmpl_context as c, url
34 from pylons.controllers.util import abort, redirect
34 from pylons.controllers.util import abort, redirect
35 from pylons.i18n.translation import _
35 from pylons.i18n.translation import _
36
36
37 from rhodecode.lib import helpers as h
37 from rhodecode.lib import helpers as h
38 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
38 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
39 HasPermissionAnyDecorator
39 HasPermissionAnyDecorator
40 from rhodecode.lib.base import BaseController, render
40 from rhodecode.lib.base import BaseController, render
41 from rhodecode.lib.utils import invalidate_cache, action_logger, repo_name_slug
41 from rhodecode.lib.utils import invalidate_cache, action_logger, repo_name_slug
42 from rhodecode.lib.helpers import get_token
42 from rhodecode.lib.helpers import get_token
43 from rhodecode.model.db import User, Repository, UserFollowing, Group
43 from rhodecode.model.db import User, Repository, UserFollowing, Group
44 from rhodecode.model.forms import RepoForm
44 from rhodecode.model.forms import RepoForm
45 from rhodecode.model.scm import ScmModel
45 from rhodecode.model.scm import ScmModel
46 from rhodecode.model.repo import RepoModel
46 from rhodecode.model.repo import RepoModel
47 from sqlalchemy.exc import IntegrityError
47 from sqlalchemy.exc import IntegrityError
48
48
49 log = logging.getLogger(__name__)
49 log = logging.getLogger(__name__)
50
50
51
51
52 class ReposController(BaseController):
52 class ReposController(BaseController):
53 """
53 """
54 REST Controller styled on the Atom Publishing Protocol"""
54 REST Controller styled on the Atom Publishing Protocol"""
55 # To properly map this controller, ensure your config/routing.py
55 # To properly map this controller, ensure your config/routing.py
56 # file has a resource setup:
56 # file has a resource setup:
57 # map.resource('repo', 'repos')
57 # map.resource('repo', 'repos')
58
58
59 @LoginRequired()
59 @LoginRequired()
60 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
60 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
61 def __before__(self):
61 def __before__(self):
62 c.admin_user = session.get('admin_user')
62 c.admin_user = session.get('admin_user')
63 c.admin_username = session.get('admin_username')
63 c.admin_username = session.get('admin_username')
64 super(ReposController, self).__before__()
64 super(ReposController, self).__before__()
65
65
66 def __load_defaults(self):
66 def __load_defaults(self):
67 c.repo_groups = Group.groups_choices()
68 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
69
67 repo_model = RepoModel()
70 repo_model = RepoModel()
68
69 c.repo_groups = [('', '')]
70 parents_link = lambda k: h.literal('&raquo;'.join(
71 map(lambda k: k.group_name,
72 k.parents + [k])
73 )
74 )
75
76 c.repo_groups.extend([(x.group_id, parents_link(x)) for \
77 x in self.sa.query(Group).all()])
78 c.repo_groups = sorted(c.repo_groups,
79 key=lambda t: t[1].split('&raquo;')[0])
80 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
81 c.users_array = repo_model.get_users_js()
71 c.users_array = repo_model.get_users_js()
82 c.users_groups_array = repo_model.get_users_groups_js()
72 c.users_groups_array = repo_model.get_users_groups_js()
83
73
84 def __load_data(self, repo_name=None):
74 def __load_data(self, repo_name=None):
85 """
75 """
86 Load defaults settings for edit, and update
76 Load defaults settings for edit, and update
87
77
88 :param repo_name:
78 :param repo_name:
89 """
79 """
90 self.__load_defaults()
80 self.__load_defaults()
91
81
92 c.repo_info = db_repo = Repository.by_repo_name(repo_name)
82 c.repo_info = db_repo = Repository.get_by_repo_name(repo_name)
93 repo = scm_repo = db_repo.scm_instance
83 repo = db_repo.scm_instance
94
84
95 if c.repo_info is None:
85 if c.repo_info is None:
96 h.flash(_('%s repository is not mapped to db perhaps'
86 h.flash(_('%s repository is not mapped to db perhaps'
97 ' it was created or renamed from the filesystem'
87 ' it was created or renamed from the filesystem'
98 ' please run the application again'
88 ' please run the application again'
99 ' in order to rescan repositories') % repo_name,
89 ' in order to rescan repositories') % repo_name,
100 category='error')
90 category='error')
101
91
102 return redirect(url('repos'))
92 return redirect(url('repos'))
103
93
104 c.default_user_id = User.by_username('default').user_id
94 c.default_user_id = User.get_by_username('default').user_id
105 c.in_public_journal = self.sa.query(UserFollowing)\
95 c.in_public_journal = self.sa.query(UserFollowing)\
106 .filter(UserFollowing.user_id == c.default_user_id)\
96 .filter(UserFollowing.user_id == c.default_user_id)\
107 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
97 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
108
98
109 if c.repo_info.stats:
99 if c.repo_info.stats:
110 last_rev = c.repo_info.stats.stat_on_revision
100 last_rev = c.repo_info.stats.stat_on_revision
111 else:
101 else:
112 last_rev = 0
102 last_rev = 0
113 c.stats_revision = last_rev
103 c.stats_revision = last_rev
114
104
115 c.repo_last_rev = repo.count() - 1 if repo.revisions else 0
105 c.repo_last_rev = repo.count() - 1 if repo.revisions else 0
116
106
117 if last_rev == 0 or c.repo_last_rev == 0:
107 if last_rev == 0 or c.repo_last_rev == 0:
118 c.stats_percentage = 0
108 c.stats_percentage = 0
119 else:
109 else:
120 c.stats_percentage = '%.2f' % ((float((last_rev)) /
110 c.stats_percentage = '%.2f' % ((float((last_rev)) /
121 c.repo_last_rev) * 100)
111 c.repo_last_rev) * 100)
122
112
123 defaults = c.repo_info.get_dict()
113 defaults = c.repo_info.get_dict()
124 group, repo_name = c.repo_info.groups_and_repo
114 group, repo_name = c.repo_info.groups_and_repo
125 defaults['repo_name'] = repo_name
115 defaults['repo_name'] = repo_name
126 defaults['repo_group'] = getattr(group[-1] if group else None,
116 defaults['repo_group'] = getattr(group[-1] if group else None,
127 'group_id', None)
117 'group_id', None)
128
118
129 #fill owner
119 #fill owner
130 if c.repo_info.user:
120 if c.repo_info.user:
131 defaults.update({'user': c.repo_info.user.username})
121 defaults.update({'user': c.repo_info.user.username})
132 else:
122 else:
133 replacement_user = self.sa.query(User)\
123 replacement_user = self.sa.query(User)\
134 .filter(User.admin == True).first().username
124 .filter(User.admin == True).first().username
135 defaults.update({'user': replacement_user})
125 defaults.update({'user': replacement_user})
136
126
137 #fill repository users
127 #fill repository users
138 for p in c.repo_info.repo_to_perm:
128 for p in c.repo_info.repo_to_perm:
139 defaults.update({'u_perm_%s' % p.user.username:
129 defaults.update({'u_perm_%s' % p.user.username:
140 p.permission.permission_name})
130 p.permission.permission_name})
141
131
142 #fill repository groups
132 #fill repository groups
143 for p in c.repo_info.users_group_to_perm:
133 for p in c.repo_info.users_group_to_perm:
144 defaults.update({'g_perm_%s' % p.users_group.users_group_name:
134 defaults.update({'g_perm_%s' % p.users_group.users_group_name:
145 p.permission.permission_name})
135 p.permission.permission_name})
146
136
147 return defaults
137 return defaults
148
138
149 @HasPermissionAllDecorator('hg.admin')
139 @HasPermissionAllDecorator('hg.admin')
150 def index(self, format='html'):
140 def index(self, format='html'):
151 """GET /repos: All items in the collection"""
141 """GET /repos: All items in the collection"""
152 # url('repos')
142 # url('repos')
153
143
154 c.repos_list = ScmModel().get_repos(Repository.query()
144 c.repos_list = ScmModel().get_repos(Repository.query()
155 .order_by(Repository.repo_name)
145 .order_by(Repository.repo_name)
156 .all(), sort_key='name_sort')
146 .all(), sort_key='name_sort')
157 return render('admin/repos/repos.html')
147 return render('admin/repos/repos.html')
158
148
159 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
149 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
160 def create(self):
150 def create(self):
161 """
151 """
162 POST /repos: Create a new item"""
152 POST /repos: Create a new item"""
163 # url('repos')
153 # url('repos')
164 repo_model = RepoModel()
154 repo_model = RepoModel()
165 self.__load_defaults()
155 self.__load_defaults()
166 form_result = {}
156 form_result = {}
167 try:
157 try:
168 form_result = RepoForm(repo_groups=c.repo_groups_choices)()\
158 form_result = RepoForm(repo_groups=c.repo_groups_choices)()\
169 .to_python(dict(request.POST))
159 .to_python(dict(request.POST))
170 repo_model.create(form_result, self.rhodecode_user)
160 repo_model.create(form_result, self.rhodecode_user)
171 if form_result['clone_uri']:
161 if form_result['clone_uri']:
172 h.flash(_('created repository %s from %s') \
162 h.flash(_('created repository %s from %s') \
173 % (form_result['repo_name'], form_result['clone_uri']),
163 % (form_result['repo_name'], form_result['clone_uri']),
174 category='success')
164 category='success')
175 else:
165 else:
176 h.flash(_('created repository %s') % form_result['repo_name'],
166 h.flash(_('created repository %s') % form_result['repo_name'],
177 category='success')
167 category='success')
178
168
179 if request.POST.get('user_created'):
169 if request.POST.get('user_created'):
180 #created by regular non admin user
170 #created by regular non admin user
181 action_logger(self.rhodecode_user, 'user_created_repo',
171 action_logger(self.rhodecode_user, 'user_created_repo',
182 form_result['repo_name_full'], '', self.sa)
172 form_result['repo_name_full'], '', self.sa)
183 else:
173 else:
184 action_logger(self.rhodecode_user, 'admin_created_repo',
174 action_logger(self.rhodecode_user, 'admin_created_repo',
185 form_result['repo_name_full'], '', self.sa)
175 form_result['repo_name_full'], '', self.sa)
186
176
187 except formencode.Invalid, errors:
177 except formencode.Invalid, errors:
188
178
189 c.new_repo = errors.value['repo_name']
179 c.new_repo = errors.value['repo_name']
190
180
191 if request.POST.get('user_created'):
181 if request.POST.get('user_created'):
192 r = render('admin/repos/repo_add_create_repository.html')
182 r = render('admin/repos/repo_add_create_repository.html')
193 else:
183 else:
194 r = render('admin/repos/repo_add.html')
184 r = render('admin/repos/repo_add.html')
195
185
196 return htmlfill.render(
186 return htmlfill.render(
197 r,
187 r,
198 defaults=errors.value,
188 defaults=errors.value,
199 errors=errors.error_dict or {},
189 errors=errors.error_dict or {},
200 prefix_error=False,
190 prefix_error=False,
201 encoding="UTF-8")
191 encoding="UTF-8")
202
192
203 except Exception:
193 except Exception:
204 log.error(traceback.format_exc())
194 log.error(traceback.format_exc())
205 msg = _('error occurred during creation of repository %s') \
195 msg = _('error occurred during creation of repository %s') \
206 % form_result.get('repo_name')
196 % form_result.get('repo_name')
207 h.flash(msg, category='error')
197 h.flash(msg, category='error')
208 if request.POST.get('user_created'):
198 if request.POST.get('user_created'):
209 return redirect(url('home'))
199 return redirect(url('home'))
210 return redirect(url('repos'))
200 return redirect(url('repos'))
211
201
212 @HasPermissionAllDecorator('hg.admin')
202 @HasPermissionAllDecorator('hg.admin')
213 def new(self, format='html'):
203 def new(self, format='html'):
214 """GET /repos/new: Form to create a new item"""
204 """GET /repos/new: Form to create a new item"""
215 new_repo = request.GET.get('repo', '')
205 new_repo = request.GET.get('repo', '')
216 c.new_repo = repo_name_slug(new_repo)
206 c.new_repo = repo_name_slug(new_repo)
217 self.__load_defaults()
207 self.__load_defaults()
218 return render('admin/repos/repo_add.html')
208 return render('admin/repos/repo_add.html')
219
209
220 @HasPermissionAllDecorator('hg.admin')
210 @HasPermissionAllDecorator('hg.admin')
221 def update(self, repo_name):
211 def update(self, repo_name):
222 """
212 """
223 PUT /repos/repo_name: Update an existing item"""
213 PUT /repos/repo_name: Update an existing item"""
224 # Forms posted to this method should contain a hidden field:
214 # Forms posted to this method should contain a hidden field:
225 # <input type="hidden" name="_method" value="PUT" />
215 # <input type="hidden" name="_method" value="PUT" />
226 # Or using helpers:
216 # Or using helpers:
227 # h.form(url('repo', repo_name=ID),
217 # h.form(url('repo', repo_name=ID),
228 # method='put')
218 # method='put')
229 # url('repo', repo_name=ID)
219 # url('repo', repo_name=ID)
230 self.__load_defaults()
220 self.__load_defaults()
231 repo_model = RepoModel()
221 repo_model = RepoModel()
232 changed_name = repo_name
222 changed_name = repo_name
233 _form = RepoForm(edit=True, old_data={'repo_name': repo_name},
223 _form = RepoForm(edit=True, old_data={'repo_name': repo_name},
234 repo_groups=c.repo_groups_choices)()
224 repo_groups=c.repo_groups_choices)()
235 try:
225 try:
236 form_result = _form.to_python(dict(request.POST))
226 form_result = _form.to_python(dict(request.POST))
237 repo_model.update(repo_name, form_result)
227 repo = repo_model.update(repo_name, form_result)
238 invalidate_cache('get_repo_cached_%s' % repo_name)
228 invalidate_cache('get_repo_cached_%s' % repo_name)
239 h.flash(_('Repository %s updated successfully' % repo_name),
229 h.flash(_('Repository %s updated successfully' % repo_name),
240 category='success')
230 category='success')
241 changed_name = form_result['repo_name_full']
231 changed_name = repo.repo_name
242 action_logger(self.rhodecode_user, 'admin_updated_repo',
232 action_logger(self.rhodecode_user, 'admin_updated_repo',
243 changed_name, '', self.sa)
233 changed_name, '', self.sa)
244
234
245 except formencode.Invalid, errors:
235 except formencode.Invalid, errors:
246 defaults = self.__load_data(repo_name)
236 defaults = self.__load_data(repo_name)
247 defaults.update(errors.value)
237 defaults.update(errors.value)
248 return htmlfill.render(
238 return htmlfill.render(
249 render('admin/repos/repo_edit.html'),
239 render('admin/repos/repo_edit.html'),
250 defaults=defaults,
240 defaults=defaults,
251 errors=errors.error_dict or {},
241 errors=errors.error_dict or {},
252 prefix_error=False,
242 prefix_error=False,
253 encoding="UTF-8")
243 encoding="UTF-8")
254
244
255 except Exception:
245 except Exception:
256 log.error(traceback.format_exc())
246 log.error(traceback.format_exc())
257 h.flash(_('error occurred during update of repository %s') \
247 h.flash(_('error occurred during update of repository %s') \
258 % repo_name, category='error')
248 % repo_name, category='error')
259 return redirect(url('edit_repo', repo_name=changed_name))
249 return redirect(url('edit_repo', repo_name=changed_name))
260
250
261 @HasPermissionAllDecorator('hg.admin')
251 @HasPermissionAllDecorator('hg.admin')
262 def delete(self, repo_name):
252 def delete(self, repo_name):
263 """
253 """
264 DELETE /repos/repo_name: Delete an existing item"""
254 DELETE /repos/repo_name: Delete an existing item"""
265 # Forms posted to this method should contain a hidden field:
255 # Forms posted to this method should contain a hidden field:
266 # <input type="hidden" name="_method" value="DELETE" />
256 # <input type="hidden" name="_method" value="DELETE" />
267 # Or using helpers:
257 # Or using helpers:
268 # h.form(url('repo', repo_name=ID),
258 # h.form(url('repo', repo_name=ID),
269 # method='delete')
259 # method='delete')
270 # url('repo', repo_name=ID)
260 # url('repo', repo_name=ID)
271
261
272 repo_model = RepoModel()
262 repo_model = RepoModel()
273 repo = repo_model.get_by_repo_name(repo_name)
263 repo = repo_model.get_by_repo_name(repo_name)
274 if not repo:
264 if not repo:
275 h.flash(_('%s repository is not mapped to db perhaps'
265 h.flash(_('%s repository is not mapped to db perhaps'
276 ' it was moved or renamed from the filesystem'
266 ' it was moved or renamed from the filesystem'
277 ' please run the application again'
267 ' please run the application again'
278 ' in order to rescan repositories') % repo_name,
268 ' in order to rescan repositories') % repo_name,
279 category='error')
269 category='error')
280
270
281 return redirect(url('repos'))
271 return redirect(url('repos'))
282 try:
272 try:
283 action_logger(self.rhodecode_user, 'admin_deleted_repo',
273 action_logger(self.rhodecode_user, 'admin_deleted_repo',
284 repo_name, '', self.sa)
274 repo_name, '', self.sa)
285 repo_model.delete(repo)
275 repo_model.delete(repo)
286 invalidate_cache('get_repo_cached_%s' % repo_name)
276 invalidate_cache('get_repo_cached_%s' % repo_name)
287 h.flash(_('deleted repository %s') % repo_name, category='success')
277 h.flash(_('deleted repository %s') % repo_name, category='success')
288
278
289 except IntegrityError, e:
279 except IntegrityError, e:
290 if e.message.find('repositories_fork_id_fkey'):
280 if e.message.find('repositories_fork_id_fkey'):
291 log.error(traceback.format_exc())
281 log.error(traceback.format_exc())
292 h.flash(_('Cannot delete %s it still contains attached '
282 h.flash(_('Cannot delete %s it still contains attached '
293 'forks') % repo_name,
283 'forks') % repo_name,
294 category='warning')
284 category='warning')
295 else:
285 else:
296 log.error(traceback.format_exc())
286 log.error(traceback.format_exc())
297 h.flash(_('An error occurred during '
287 h.flash(_('An error occurred during '
298 'deletion of %s') % repo_name,
288 'deletion of %s') % repo_name,
299 category='error')
289 category='error')
300
290
301 except Exception, e:
291 except Exception, e:
302 log.error(traceback.format_exc())
292 log.error(traceback.format_exc())
303 h.flash(_('An error occurred during deletion of %s') % repo_name,
293 h.flash(_('An error occurred during deletion of %s') % repo_name,
304 category='error')
294 category='error')
305
295
306 return redirect(url('repos'))
296 return redirect(url('repos'))
307
297
308 @HasPermissionAllDecorator('hg.admin')
298 @HasPermissionAllDecorator('hg.admin')
309 def delete_perm_user(self, repo_name):
299 def delete_perm_user(self, repo_name):
310 """
300 """
311 DELETE an existing repository permission user
301 DELETE an existing repository permission user
312
302
313 :param repo_name:
303 :param repo_name:
314 """
304 """
315
305
316 try:
306 try:
317 repo_model = RepoModel()
307 repo_model = RepoModel()
318 repo_model.delete_perm_user(request.POST, repo_name)
308 repo_model.delete_perm_user(request.POST, repo_name)
319 except Exception, e:
309 except Exception, e:
320 h.flash(_('An error occurred during deletion of repository user'),
310 h.flash(_('An error occurred during deletion of repository user'),
321 category='error')
311 category='error')
322 raise HTTPInternalServerError()
312 raise HTTPInternalServerError()
323
313
324 @HasPermissionAllDecorator('hg.admin')
314 @HasPermissionAllDecorator('hg.admin')
325 def delete_perm_users_group(self, repo_name):
315 def delete_perm_users_group(self, repo_name):
326 """
316 """
327 DELETE an existing repository permission users group
317 DELETE an existing repository permission users group
328
318
329 :param repo_name:
319 :param repo_name:
330 """
320 """
331 try:
321 try:
332 repo_model = RepoModel()
322 repo_model = RepoModel()
333 repo_model.delete_perm_users_group(request.POST, repo_name)
323 repo_model.delete_perm_users_group(request.POST, repo_name)
334 except Exception, e:
324 except Exception, e:
335 h.flash(_('An error occurred during deletion of repository'
325 h.flash(_('An error occurred during deletion of repository'
336 ' users groups'),
326 ' users groups'),
337 category='error')
327 category='error')
338 raise HTTPInternalServerError()
328 raise HTTPInternalServerError()
339
329
340 @HasPermissionAllDecorator('hg.admin')
330 @HasPermissionAllDecorator('hg.admin')
341 def repo_stats(self, repo_name):
331 def repo_stats(self, repo_name):
342 """
332 """
343 DELETE an existing repository statistics
333 DELETE an existing repository statistics
344
334
345 :param repo_name:
335 :param repo_name:
346 """
336 """
347
337
348 try:
338 try:
349 repo_model = RepoModel()
339 repo_model = RepoModel()
350 repo_model.delete_stats(repo_name)
340 repo_model.delete_stats(repo_name)
351 except Exception, e:
341 except Exception, e:
352 h.flash(_('An error occurred during deletion of repository stats'),
342 h.flash(_('An error occurred during deletion of repository stats'),
353 category='error')
343 category='error')
354 return redirect(url('edit_repo', repo_name=repo_name))
344 return redirect(url('edit_repo', repo_name=repo_name))
355
345
356 @HasPermissionAllDecorator('hg.admin')
346 @HasPermissionAllDecorator('hg.admin')
357 def repo_cache(self, repo_name):
347 def repo_cache(self, repo_name):
358 """
348 """
359 INVALIDATE existing repository cache
349 INVALIDATE existing repository cache
360
350
361 :param repo_name:
351 :param repo_name:
362 """
352 """
363
353
364 try:
354 try:
365 ScmModel().mark_for_invalidation(repo_name)
355 ScmModel().mark_for_invalidation(repo_name)
366 except Exception, e:
356 except Exception, e:
367 h.flash(_('An error occurred during cache invalidation'),
357 h.flash(_('An error occurred during cache invalidation'),
368 category='error')
358 category='error')
369 return redirect(url('edit_repo', repo_name=repo_name))
359 return redirect(url('edit_repo', repo_name=repo_name))
370
360
371 @HasPermissionAllDecorator('hg.admin')
361 @HasPermissionAllDecorator('hg.admin')
372 def repo_public_journal(self, repo_name):
362 def repo_public_journal(self, repo_name):
373 """
363 """
374 Set's this repository to be visible in public journal,
364 Set's this repository to be visible in public journal,
375 in other words assing default user to follow this repo
365 in other words assing default user to follow this repo
376
366
377 :param repo_name:
367 :param repo_name:
378 """
368 """
379
369
380 cur_token = request.POST.get('auth_token')
370 cur_token = request.POST.get('auth_token')
381 token = get_token()
371 token = get_token()
382 if cur_token == token:
372 if cur_token == token:
383 try:
373 try:
384 repo_id = Repository.by_repo_name(repo_name).repo_id
374 repo_id = Repository.get_by_repo_name(repo_name).repo_id
385 user_id = User.by_username('default').user_id
375 user_id = User.get_by_username('default').user_id
386 self.scm_model.toggle_following_repo(repo_id, user_id)
376 self.scm_model.toggle_following_repo(repo_id, user_id)
387 h.flash(_('Updated repository visibility in public journal'),
377 h.flash(_('Updated repository visibility in public journal'),
388 category='success')
378 category='success')
389 except:
379 except:
390 h.flash(_('An error occurred during setting this'
380 h.flash(_('An error occurred during setting this'
391 ' repository in public journal'),
381 ' repository in public journal'),
392 category='error')
382 category='error')
393
383
394 else:
384 else:
395 h.flash(_('Token mismatch'), category='error')
385 h.flash(_('Token mismatch'), category='error')
396 return redirect(url('edit_repo', repo_name=repo_name))
386 return redirect(url('edit_repo', repo_name=repo_name))
397
387
398 @HasPermissionAllDecorator('hg.admin')
388 @HasPermissionAllDecorator('hg.admin')
399 def repo_pull(self, repo_name):
389 def repo_pull(self, repo_name):
400 """
390 """
401 Runs task to update given repository with remote changes,
391 Runs task to update given repository with remote changes,
402 ie. make pull on remote location
392 ie. make pull on remote location
403
393
404 :param repo_name:
394 :param repo_name:
405 """
395 """
406 try:
396 try:
407 ScmModel().pull_changes(repo_name, self.rhodecode_user.username)
397 ScmModel().pull_changes(repo_name, self.rhodecode_user.username)
408 h.flash(_('Pulled from remote location'), category='success')
398 h.flash(_('Pulled from remote location'), category='success')
409 except Exception, e:
399 except Exception, e:
410 h.flash(_('An error occurred during pull from remote location'),
400 h.flash(_('An error occurred during pull from remote location'),
411 category='error')
401 category='error')
412
402
413 return redirect(url('edit_repo', repo_name=repo_name))
403 return redirect(url('edit_repo', repo_name=repo_name))
414
404
415 @HasPermissionAllDecorator('hg.admin')
405 @HasPermissionAllDecorator('hg.admin')
416 def show(self, repo_name, format='html'):
406 def show(self, repo_name, format='html'):
417 """GET /repos/repo_name: Show a specific item"""
407 """GET /repos/repo_name: Show a specific item"""
418 # url('repo', repo_name=ID)
408 # url('repo', repo_name=ID)
419
409
420 @HasPermissionAllDecorator('hg.admin')
410 @HasPermissionAllDecorator('hg.admin')
421 def edit(self, repo_name, format='html'):
411 def edit(self, repo_name, format='html'):
422 """GET /repos/repo_name/edit: Form to edit an existing item"""
412 """GET /repos/repo_name/edit: Form to edit an existing item"""
423 # url('edit_repo', repo_name=ID)
413 # url('edit_repo', repo_name=ID)
424 defaults = self.__load_data(repo_name)
414 defaults = self.__load_data(repo_name)
425
415
426 return htmlfill.render(
416 return htmlfill.render(
427 render('admin/repos/repo_edit.html'),
417 render('admin/repos/repo_edit.html'),
428 defaults=defaults,
418 defaults=defaults,
429 encoding="UTF-8",
419 encoding="UTF-8",
430 force_defaults=False
420 force_defaults=False
431 )
421 )
@@ -1,223 +1,229
1 import logging
1 import logging
2 import traceback
2 import traceback
3 import formencode
3 import formencode
4
4
5 from formencode import htmlfill
5 from formencode import htmlfill
6 from operator import itemgetter
6 from operator import itemgetter
7
7
8 from pylons import request, response, session, tmpl_context as c, url
8 from pylons import request, response, session, tmpl_context as c, url
9 from pylons.controllers.util import abort, redirect
9 from pylons.controllers.util import abort, redirect
10 from pylons.i18n.translation import _
10 from pylons.i18n.translation import _
11
11
12 from sqlalchemy.exc import IntegrityError
13
12 from rhodecode.lib import helpers as h
14 from rhodecode.lib import helpers as h
13 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
15 from rhodecode.lib.auth import LoginRequired, HasPermissionAnyDecorator
14 HasPermissionAnyDecorator
15 from rhodecode.lib.base import BaseController, render
16 from rhodecode.lib.base import BaseController, render
16 from rhodecode.model.db import Group
17 from rhodecode.model.db import Group
17 from rhodecode.model.repos_group import ReposGroupModel
18 from rhodecode.model.repos_group import ReposGroupModel
18 from rhodecode.model.forms import ReposGroupForm
19 from rhodecode.model.forms import ReposGroupForm
19
20
20 log = logging.getLogger(__name__)
21 log = logging.getLogger(__name__)
21
22
22
23
23 class ReposGroupsController(BaseController):
24 class ReposGroupsController(BaseController):
24 """REST Controller styled on the Atom Publishing Protocol"""
25 """REST Controller styled on the Atom Publishing Protocol"""
25 # To properly map this controller, ensure your config/routing.py
26 # To properly map this controller, ensure your config/routing.py
26 # file has a resource setup:
27 # file has a resource setup:
27 # map.resource('repos_group', 'repos_groups')
28 # map.resource('repos_group', 'repos_groups')
28
29
29 @LoginRequired()
30 @LoginRequired()
30 def __before__(self):
31 def __before__(self):
31 super(ReposGroupsController, self).__before__()
32 super(ReposGroupsController, self).__before__()
32
33
33 def __load_defaults(self):
34 def __load_defaults(self):
34
35 c.repo_groups = Group.groups_choices()
35 c.repo_groups = [('', '')]
36 parents_link = lambda k: h.literal('&raquo;'.join(
37 map(lambda k: k.group_name,
38 k.parents + [k])
39 )
40 )
41
42 c.repo_groups.extend([(x.group_id, parents_link(x)) for \
43 x in self.sa.query(Group).all()])
44
45 c.repo_groups = sorted(c.repo_groups,
46 key=lambda t: t[1].split('&raquo;')[0])
47 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
36 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
48
37
49 def __load_data(self, group_id):
38 def __load_data(self, group_id):
50 """
39 """
51 Load defaults settings for edit, and update
40 Load defaults settings for edit, and update
52
41
53 :param group_id:
42 :param group_id:
54 """
43 """
55 self.__load_defaults()
44 self.__load_defaults()
56
45
57 repo_group = Group.get(group_id)
46 repo_group = Group.get(group_id)
58
47
59 data = repo_group.get_dict()
48 data = repo_group.get_dict()
60
49
50 data['group_name'] = repo_group.name
51
61 return data
52 return data
62
53
63 @HasPermissionAnyDecorator('hg.admin')
54 @HasPermissionAnyDecorator('hg.admin')
64 def index(self, format='html'):
55 def index(self, format='html'):
65 """GET /repos_groups: All items in the collection"""
56 """GET /repos_groups: All items in the collection"""
66 # url('repos_groups')
57 # url('repos_groups')
67
58
68 sk = lambda g:g.parents[0].group_name if g.parents else g.group_name
59 sk = lambda g:g.parents[0].group_name if g.parents else g.group_name
69 c.groups = sorted(Group.query().all(), key=sk)
60 c.groups = sorted(Group.query().all(), key=sk)
70 return render('admin/repos_groups/repos_groups_show.html')
61 return render('admin/repos_groups/repos_groups_show.html')
71
62
72 @HasPermissionAnyDecorator('hg.admin')
63 @HasPermissionAnyDecorator('hg.admin')
73 def create(self):
64 def create(self):
74 """POST /repos_groups: Create a new item"""
65 """POST /repos_groups: Create a new item"""
75 # url('repos_groups')
66 # url('repos_groups')
76 self.__load_defaults()
67 self.__load_defaults()
77 repos_group_model = ReposGroupModel()
68 repos_group_model = ReposGroupModel()
78 repos_group_form = ReposGroupForm(available_groups=
69 repos_group_form = ReposGroupForm(available_groups=
79 c.repo_groups_choices)()
70 c.repo_groups_choices)()
80 try:
71 try:
81 form_result = repos_group_form.to_python(dict(request.POST))
72 form_result = repos_group_form.to_python(dict(request.POST))
82 repos_group_model.create(form_result)
73 repos_group_model.create(form_result)
83 h.flash(_('created repos group %s') \
74 h.flash(_('created repos group %s') \
84 % form_result['group_name'], category='success')
75 % form_result['group_name'], category='success')
85 #TODO: in futureaction_logger(, '', '', '', self.sa)
76 #TODO: in futureaction_logger(, '', '', '', self.sa)
86 except formencode.Invalid, errors:
77 except formencode.Invalid, errors:
87
78
88 return htmlfill.render(
79 return htmlfill.render(
89 render('admin/repos_groups/repos_groups_add.html'),
80 render('admin/repos_groups/repos_groups_add.html'),
90 defaults=errors.value,
81 defaults=errors.value,
91 errors=errors.error_dict or {},
82 errors=errors.error_dict or {},
92 prefix_error=False,
83 prefix_error=False,
93 encoding="UTF-8")
84 encoding="UTF-8")
94 except Exception:
85 except Exception:
95 log.error(traceback.format_exc())
86 log.error(traceback.format_exc())
96 h.flash(_('error occurred during creation of repos group %s') \
87 h.flash(_('error occurred during creation of repos group %s') \
97 % request.POST.get('group_name'), category='error')
88 % request.POST.get('group_name'), category='error')
98
89
99 return redirect(url('repos_groups'))
90 return redirect(url('repos_groups'))
100
91
101
92
102 @HasPermissionAnyDecorator('hg.admin')
93 @HasPermissionAnyDecorator('hg.admin')
103 def new(self, format='html'):
94 def new(self, format='html'):
104 """GET /repos_groups/new: Form to create a new item"""
95 """GET /repos_groups/new: Form to create a new item"""
105 # url('new_repos_group')
96 # url('new_repos_group')
106 self.__load_defaults()
97 self.__load_defaults()
107 return render('admin/repos_groups/repos_groups_add.html')
98 return render('admin/repos_groups/repos_groups_add.html')
108
99
109 @HasPermissionAnyDecorator('hg.admin')
100 @HasPermissionAnyDecorator('hg.admin')
110 def update(self, id):
101 def update(self, id):
111 """PUT /repos_groups/id: Update an existing item"""
102 """PUT /repos_groups/id: Update an existing item"""
112 # Forms posted to this method should contain a hidden field:
103 # Forms posted to this method should contain a hidden field:
113 # <input type="hidden" name="_method" value="PUT" />
104 # <input type="hidden" name="_method" value="PUT" />
114 # Or using helpers:
105 # Or using helpers:
115 # h.form(url('repos_group', id=ID),
106 # h.form(url('repos_group', id=ID),
116 # method='put')
107 # method='put')
117 # url('repos_group', id=ID)
108 # url('repos_group', id=ID)
118
109
119 self.__load_defaults()
110 self.__load_defaults()
120 c.repos_group = Group.get(id)
111 c.repos_group = Group.get(id)
121
112
122 repos_group_model = ReposGroupModel()
113 repos_group_model = ReposGroupModel()
123 repos_group_form = ReposGroupForm(edit=True,
114 repos_group_form = ReposGroupForm(edit=True,
124 old_data=c.repos_group.get_dict(),
115 old_data=c.repos_group.get_dict(),
125 available_groups=
116 available_groups=
126 c.repo_groups_choices)()
117 c.repo_groups_choices)()
127 try:
118 try:
128 form_result = repos_group_form.to_python(dict(request.POST))
119 form_result = repos_group_form.to_python(dict(request.POST))
129 repos_group_model.update(id, form_result)
120 repos_group_model.update(id, form_result)
130 h.flash(_('updated repos group %s') \
121 h.flash(_('updated repos group %s') \
131 % form_result['group_name'], category='success')
122 % form_result['group_name'], category='success')
132 #TODO: in futureaction_logger(, '', '', '', self.sa)
123 #TODO: in futureaction_logger(, '', '', '', self.sa)
133 except formencode.Invalid, errors:
124 except formencode.Invalid, errors:
134
125
135 return htmlfill.render(
126 return htmlfill.render(
136 render('admin/repos_groups/repos_groups_edit.html'),
127 render('admin/repos_groups/repos_groups_edit.html'),
137 defaults=errors.value,
128 defaults=errors.value,
138 errors=errors.error_dict or {},
129 errors=errors.error_dict or {},
139 prefix_error=False,
130 prefix_error=False,
140 encoding="UTF-8")
131 encoding="UTF-8")
141 except Exception:
132 except Exception:
142 log.error(traceback.format_exc())
133 log.error(traceback.format_exc())
143 h.flash(_('error occurred during update of repos group %s') \
134 h.flash(_('error occurred during update of repos group %s') \
144 % request.POST.get('group_name'), category='error')
135 % request.POST.get('group_name'), category='error')
145
136
146 return redirect(url('repos_groups'))
137 return redirect(url('repos_groups'))
147
138
148
139
149 @HasPermissionAnyDecorator('hg.admin')
140 @HasPermissionAnyDecorator('hg.admin')
150 def delete(self, id):
141 def delete(self, id):
151 """DELETE /repos_groups/id: Delete an existing item"""
142 """DELETE /repos_groups/id: Delete an existing item"""
152 # Forms posted to this method should contain a hidden field:
143 # Forms posted to this method should contain a hidden field:
153 # <input type="hidden" name="_method" value="DELETE" />
144 # <input type="hidden" name="_method" value="DELETE" />
154 # Or using helpers:
145 # Or using helpers:
155 # h.form(url('repos_group', id=ID),
146 # h.form(url('repos_group', id=ID),
156 # method='delete')
147 # method='delete')
157 # url('repos_group', id=ID)
148 # url('repos_group', id=ID)
158
149
159 repos_group_model = ReposGroupModel()
150 repos_group_model = ReposGroupModel()
160 gr = Group.get(id)
151 gr = Group.get(id)
161 repos = gr.repositories.all()
152 repos = gr.repositories.all()
162 if repos:
153 if repos:
163 h.flash(_('This group contains %s repositores and cannot be '
154 h.flash(_('This group contains %s repositores and cannot be '
164 'deleted' % len(repos)),
155 'deleted' % len(repos)),
165 category='error')
156 category='error')
166 return redirect(url('repos_groups'))
157 return redirect(url('repos_groups'))
167
158
168 try:
159 try:
169 repos_group_model.delete(id)
160 repos_group_model.delete(id)
170 h.flash(_('removed repos group %s' % gr.group_name), category='success')
161 h.flash(_('removed repos group %s' % gr.group_name), category='success')
171 #TODO: in future action_logger(, '', '', '', self.sa)
162 #TODO: in future action_logger(, '', '', '', self.sa)
163 except IntegrityError, e:
164 if e.message.find('groups_group_parent_id_fkey'):
165 log.error(traceback.format_exc())
166 h.flash(_('Cannot delete this group it still contains '
167 'subgroups'),
168 category='warning')
169 else:
170 log.error(traceback.format_exc())
171 h.flash(_('error occurred during deletion of repos '
172 'group %s' % gr.group_name), category='error')
173
172 except Exception:
174 except Exception:
173 log.error(traceback.format_exc())
175 log.error(traceback.format_exc())
174 h.flash(_('error occurred during deletion of repos group %s' % gr.group_name),
176 h.flash(_('error occurred during deletion of repos '
175 category='error')
177 'group %s' % gr.group_name), category='error')
176
178
177 return redirect(url('repos_groups'))
179 return redirect(url('repos_groups'))
178
180
181 def show_by_name(self, group_name):
182 id_ = Group.get_by_group_name(group_name).group_id
183 return self.show(id_)
184
179 def show(self, id, format='html'):
185 def show(self, id, format='html'):
180 """GET /repos_groups/id: Show a specific item"""
186 """GET /repos_groups/id: Show a specific item"""
181 # url('repos_group', id=ID)
187 # url('repos_group', id=ID)
182
188
183 c.group = Group.get(id)
189 c.group = Group.get(id)
184
190
185 if c.group:
191 if c.group:
186 c.group_repos = c.group.repositories.all()
192 c.group_repos = c.group.repositories.all()
187 else:
193 else:
188 return redirect(url('home'))
194 return redirect(url('home'))
189
195
190 #overwrite our cached list with current filter
196 #overwrite our cached list with current filter
191 gr_filter = c.group_repos
197 gr_filter = c.group_repos
192 c.cached_repo_list = self.scm_model.get_repos(all_repos=gr_filter)
198 c.cached_repo_list = self.scm_model.get_repos(all_repos=gr_filter)
193
199
194 c.repos_list = c.cached_repo_list
200 c.repos_list = c.cached_repo_list
195
201
196 c.repo_cnt = 0
202 c.repo_cnt = 0
197
203
198 c.groups = self.sa.query(Group).order_by(Group.group_name)\
204 c.groups = self.sa.query(Group).order_by(Group.group_name)\
199 .filter(Group.group_parent_id == id).all()
205 .filter(Group.group_parent_id == id).all()
200
206
201 return render('admin/repos_groups/repos_groups.html')
207 return render('admin/repos_groups/repos_groups.html')
202
208
203 @HasPermissionAnyDecorator('hg.admin')
209 @HasPermissionAnyDecorator('hg.admin')
204 def edit(self, id, format='html'):
210 def edit(self, id, format='html'):
205 """GET /repos_groups/id/edit: Form to edit an existing item"""
211 """GET /repos_groups/id/edit: Form to edit an existing item"""
206 # url('edit_repos_group', id=ID)
212 # url('edit_repos_group', id=ID)
207
213
208 id_ = int(id)
214 id_ = int(id)
209
215
210 c.repos_group = Group.get(id_)
216 c.repos_group = Group.get(id_)
211 defaults = self.__load_data(id_)
217 defaults = self.__load_data(id_)
212
218
213 # we need to exclude this group from the group list for editing
219 # we need to exclude this group from the group list for editing
214 c.repo_groups = filter(lambda x:x[0] != id_, c.repo_groups)
220 c.repo_groups = filter(lambda x:x[0] != id_, c.repo_groups)
215
221
216 return htmlfill.render(
222 return htmlfill.render(
217 render('admin/repos_groups/repos_groups_edit.html'),
223 render('admin/repos_groups/repos_groups_edit.html'),
218 defaults=defaults,
224 defaults=defaults,
219 encoding="UTF-8",
225 encoding="UTF-8",
220 force_defaults=False
226 force_defaults=False
221 )
227 )
222
228
223
229
@@ -1,407 +1,397
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.admin.settings
3 rhodecode.controllers.admin.settings
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 settings controller for rhodecode admin
6 settings controller for rhodecode admin
7
7
8 :created_on: Jul 14, 2010
8 :created_on: Jul 14, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import logging
26 import logging
27 import traceback
27 import traceback
28 import formencode
28 import formencode
29
29
30 from sqlalchemy import func
30 from sqlalchemy import func
31 from formencode import htmlfill
31 from formencode import htmlfill
32 from pylons import request, session, tmpl_context as c, url, config
32 from pylons import request, session, tmpl_context as c, url, config
33 from pylons.controllers.util import abort, redirect
33 from pylons.controllers.util import abort, redirect
34 from pylons.i18n.translation import _
34 from pylons.i18n.translation import _
35
35
36 from rhodecode.lib import helpers as h
36 from rhodecode.lib import helpers as h
37 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
37 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
38 HasPermissionAnyDecorator, NotAnonymous
38 HasPermissionAnyDecorator, NotAnonymous
39 from rhodecode.lib.base import BaseController, render
39 from rhodecode.lib.base import BaseController, render
40 from rhodecode.lib.celerylib import tasks, run_task
40 from rhodecode.lib.celerylib import tasks, run_task
41 from rhodecode.lib.utils import repo2db_mapper, invalidate_cache, \
41 from rhodecode.lib.utils import repo2db_mapper, invalidate_cache, \
42 set_rhodecode_config, repo_name_slug
42 set_rhodecode_config, repo_name_slug
43 from rhodecode.model.db import RhodeCodeUi, Repository, Group, \
43 from rhodecode.model.db import RhodeCodeUi, Repository, Group, \
44 RhodeCodeSettings
44 RhodeCodeSettings
45 from rhodecode.model.forms import UserForm, ApplicationSettingsForm, \
45 from rhodecode.model.forms import UserForm, ApplicationSettingsForm, \
46 ApplicationUiSettingsForm
46 ApplicationUiSettingsForm
47 from rhodecode.model.scm import ScmModel
47 from rhodecode.model.scm import ScmModel
48 from rhodecode.model.user import UserModel
48 from rhodecode.model.user import UserModel
49 from rhodecode.model.db import User
49 from rhodecode.model.db import User
50
50
51 log = logging.getLogger(__name__)
51 log = logging.getLogger(__name__)
52
52
53
53
54 class SettingsController(BaseController):
54 class SettingsController(BaseController):
55 """REST Controller styled on the Atom Publishing Protocol"""
55 """REST Controller styled on the Atom Publishing Protocol"""
56 # To properly map this controller, ensure your config/routing.py
56 # To properly map this controller, ensure your config/routing.py
57 # file has a resource setup:
57 # file has a resource setup:
58 # map.resource('setting', 'settings', controller='admin/settings',
58 # map.resource('setting', 'settings', controller='admin/settings',
59 # path_prefix='/admin', name_prefix='admin_')
59 # path_prefix='/admin', name_prefix='admin_')
60
60
61 @LoginRequired()
61 @LoginRequired()
62 def __before__(self):
62 def __before__(self):
63 c.admin_user = session.get('admin_user')
63 c.admin_user = session.get('admin_user')
64 c.admin_username = session.get('admin_username')
64 c.admin_username = session.get('admin_username')
65 super(SettingsController, self).__before__()
65 super(SettingsController, self).__before__()
66
66
67 @HasPermissionAllDecorator('hg.admin')
67 @HasPermissionAllDecorator('hg.admin')
68 def index(self, format='html'):
68 def index(self, format='html'):
69 """GET /admin/settings: All items in the collection"""
69 """GET /admin/settings: All items in the collection"""
70 # url('admin_settings')
70 # url('admin_settings')
71
71
72 defaults = RhodeCodeSettings.get_app_settings()
72 defaults = RhodeCodeSettings.get_app_settings()
73 defaults.update(self.get_hg_ui_settings())
73 defaults.update(self.get_hg_ui_settings())
74 return htmlfill.render(
74 return htmlfill.render(
75 render('admin/settings/settings.html'),
75 render('admin/settings/settings.html'),
76 defaults=defaults,
76 defaults=defaults,
77 encoding="UTF-8",
77 encoding="UTF-8",
78 force_defaults=False
78 force_defaults=False
79 )
79 )
80
80
81 @HasPermissionAllDecorator('hg.admin')
81 @HasPermissionAllDecorator('hg.admin')
82 def create(self):
82 def create(self):
83 """POST /admin/settings: Create a new item"""
83 """POST /admin/settings: Create a new item"""
84 # url('admin_settings')
84 # url('admin_settings')
85
85
86 @HasPermissionAllDecorator('hg.admin')
86 @HasPermissionAllDecorator('hg.admin')
87 def new(self, format='html'):
87 def new(self, format='html'):
88 """GET /admin/settings/new: Form to create a new item"""
88 """GET /admin/settings/new: Form to create a new item"""
89 # url('admin_new_setting')
89 # url('admin_new_setting')
90
90
91 @HasPermissionAllDecorator('hg.admin')
91 @HasPermissionAllDecorator('hg.admin')
92 def update(self, setting_id):
92 def update(self, setting_id):
93 """PUT /admin/settings/setting_id: Update an existing item"""
93 """PUT /admin/settings/setting_id: Update an existing item"""
94 # Forms posted to this method should contain a hidden field:
94 # Forms posted to this method should contain a hidden field:
95 # <input type="hidden" name="_method" value="PUT" />
95 # <input type="hidden" name="_method" value="PUT" />
96 # Or using helpers:
96 # Or using helpers:
97 # h.form(url('admin_setting', setting_id=ID),
97 # h.form(url('admin_setting', setting_id=ID),
98 # method='put')
98 # method='put')
99 # url('admin_setting', setting_id=ID)
99 # url('admin_setting', setting_id=ID)
100 if setting_id == 'mapping':
100 if setting_id == 'mapping':
101 rm_obsolete = request.POST.get('destroy', False)
101 rm_obsolete = request.POST.get('destroy', False)
102 log.debug('Rescanning directories with destroy=%s', rm_obsolete)
102 log.debug('Rescanning directories with destroy=%s', rm_obsolete)
103 initial = ScmModel().repo_scan()
103 initial = ScmModel().repo_scan()
104 log.debug('invalidating all repositories')
104 log.debug('invalidating all repositories')
105 for repo_name in initial.keys():
105 for repo_name in initial.keys():
106 invalidate_cache('get_repo_cached_%s' % repo_name)
106 invalidate_cache('get_repo_cached_%s' % repo_name)
107
107
108 added, removed = repo2db_mapper(initial, rm_obsolete)
108 added, removed = repo2db_mapper(initial, rm_obsolete)
109
109
110 h.flash(_('Repositories successfully'
110 h.flash(_('Repositories successfully'
111 ' rescanned added: %s,removed: %s') % (added, removed),
111 ' rescanned added: %s,removed: %s') % (added, removed),
112 category='success')
112 category='success')
113
113
114 if setting_id == 'whoosh':
114 if setting_id == 'whoosh':
115 repo_location = self.get_hg_ui_settings()['paths_root_path']
115 repo_location = self.get_hg_ui_settings()['paths_root_path']
116 full_index = request.POST.get('full_index', False)
116 full_index = request.POST.get('full_index', False)
117 run_task(tasks.whoosh_index, repo_location, full_index)
117 run_task(tasks.whoosh_index, repo_location, full_index)
118
118
119 h.flash(_('Whoosh reindex task scheduled'), category='success')
119 h.flash(_('Whoosh reindex task scheduled'), category='success')
120 if setting_id == 'global':
120 if setting_id == 'global':
121
121
122 application_form = ApplicationSettingsForm()()
122 application_form = ApplicationSettingsForm()()
123 try:
123 try:
124 form_result = application_form.to_python(dict(request.POST))
124 form_result = application_form.to_python(dict(request.POST))
125
125
126 try:
126 try:
127 hgsettings1 = RhodeCodeSettings.get_by_name('title')
127 hgsettings1 = RhodeCodeSettings.get_by_name('title')
128 hgsettings1.app_settings_value = \
128 hgsettings1.app_settings_value = \
129 form_result['rhodecode_title']
129 form_result['rhodecode_title']
130
130
131 hgsettings2 = RhodeCodeSettings.get_by_name('realm')
131 hgsettings2 = RhodeCodeSettings.get_by_name('realm')
132 hgsettings2.app_settings_value = \
132 hgsettings2.app_settings_value = \
133 form_result['rhodecode_realm']
133 form_result['rhodecode_realm']
134
134
135 hgsettings3 = RhodeCodeSettings.get_by_name('ga_code')
135 hgsettings3 = RhodeCodeSettings.get_by_name('ga_code')
136 hgsettings3.app_settings_value = \
136 hgsettings3.app_settings_value = \
137 form_result['rhodecode_ga_code']
137 form_result['rhodecode_ga_code']
138
138
139 self.sa.add(hgsettings1)
139 self.sa.add(hgsettings1)
140 self.sa.add(hgsettings2)
140 self.sa.add(hgsettings2)
141 self.sa.add(hgsettings3)
141 self.sa.add(hgsettings3)
142 self.sa.commit()
142 self.sa.commit()
143 set_rhodecode_config(config)
143 set_rhodecode_config(config)
144 h.flash(_('Updated application settings'),
144 h.flash(_('Updated application settings'),
145 category='success')
145 category='success')
146
146
147 except Exception:
147 except Exception:
148 log.error(traceback.format_exc())
148 log.error(traceback.format_exc())
149 h.flash(_('error occurred during updating '
149 h.flash(_('error occurred during updating '
150 'application settings'),
150 'application settings'),
151 category='error')
151 category='error')
152
152
153 self.sa.rollback()
153 self.sa.rollback()
154
154
155 except formencode.Invalid, errors:
155 except formencode.Invalid, errors:
156 return htmlfill.render(
156 return htmlfill.render(
157 render('admin/settings/settings.html'),
157 render('admin/settings/settings.html'),
158 defaults=errors.value,
158 defaults=errors.value,
159 errors=errors.error_dict or {},
159 errors=errors.error_dict or {},
160 prefix_error=False,
160 prefix_error=False,
161 encoding="UTF-8")
161 encoding="UTF-8")
162
162
163 if setting_id == 'mercurial':
163 if setting_id == 'mercurial':
164 application_form = ApplicationUiSettingsForm()()
164 application_form = ApplicationUiSettingsForm()()
165 try:
165 try:
166 form_result = application_form.to_python(dict(request.POST))
166 form_result = application_form.to_python(dict(request.POST))
167
167
168 try:
168 try:
169
169
170 hgsettings1 = self.sa.query(RhodeCodeUi)\
170 hgsettings1 = self.sa.query(RhodeCodeUi)\
171 .filter(RhodeCodeUi.ui_key == 'push_ssl').one()
171 .filter(RhodeCodeUi.ui_key == 'push_ssl').one()
172 hgsettings1.ui_value = form_result['web_push_ssl']
172 hgsettings1.ui_value = form_result['web_push_ssl']
173
173
174 hgsettings2 = self.sa.query(RhodeCodeUi)\
174 hgsettings2 = self.sa.query(RhodeCodeUi)\
175 .filter(RhodeCodeUi.ui_key == '/').one()
175 .filter(RhodeCodeUi.ui_key == '/').one()
176 hgsettings2.ui_value = form_result['paths_root_path']
176 hgsettings2.ui_value = form_result['paths_root_path']
177
177
178 #HOOKS
178 #HOOKS
179 hgsettings3 = self.sa.query(RhodeCodeUi)\
179 hgsettings3 = self.sa.query(RhodeCodeUi)\
180 .filter(RhodeCodeUi.ui_key == 'changegroup.update').one()
180 .filter(RhodeCodeUi.ui_key == 'changegroup.update').one()
181 hgsettings3.ui_active = \
181 hgsettings3.ui_active = \
182 bool(form_result['hooks_changegroup_update'])
182 bool(form_result['hooks_changegroup_update'])
183
183
184 hgsettings4 = self.sa.query(RhodeCodeUi)\
184 hgsettings4 = self.sa.query(RhodeCodeUi)\
185 .filter(RhodeCodeUi.ui_key ==
185 .filter(RhodeCodeUi.ui_key ==
186 'changegroup.repo_size').one()
186 'changegroup.repo_size').one()
187 hgsettings4.ui_active = \
187 hgsettings4.ui_active = \
188 bool(form_result['hooks_changegroup_repo_size'])
188 bool(form_result['hooks_changegroup_repo_size'])
189
189
190 hgsettings5 = self.sa.query(RhodeCodeUi)\
190 hgsettings5 = self.sa.query(RhodeCodeUi)\
191 .filter(RhodeCodeUi.ui_key ==
191 .filter(RhodeCodeUi.ui_key ==
192 'pretxnchangegroup.push_logger').one()
192 'pretxnchangegroup.push_logger').one()
193 hgsettings5.ui_active = \
193 hgsettings5.ui_active = \
194 bool(form_result['hooks_pretxnchangegroup'
194 bool(form_result['hooks_pretxnchangegroup'
195 '_push_logger'])
195 '_push_logger'])
196
196
197 hgsettings6 = self.sa.query(RhodeCodeUi)\
197 hgsettings6 = self.sa.query(RhodeCodeUi)\
198 .filter(RhodeCodeUi.ui_key ==
198 .filter(RhodeCodeUi.ui_key ==
199 'preoutgoing.pull_logger').one()
199 'preoutgoing.pull_logger').one()
200 hgsettings6.ui_active = \
200 hgsettings6.ui_active = \
201 bool(form_result['hooks_preoutgoing_pull_logger'])
201 bool(form_result['hooks_preoutgoing_pull_logger'])
202
202
203 self.sa.add(hgsettings1)
203 self.sa.add(hgsettings1)
204 self.sa.add(hgsettings2)
204 self.sa.add(hgsettings2)
205 self.sa.add(hgsettings3)
205 self.sa.add(hgsettings3)
206 self.sa.add(hgsettings4)
206 self.sa.add(hgsettings4)
207 self.sa.add(hgsettings5)
207 self.sa.add(hgsettings5)
208 self.sa.add(hgsettings6)
208 self.sa.add(hgsettings6)
209 self.sa.commit()
209 self.sa.commit()
210
210
211 h.flash(_('Updated mercurial settings'),
211 h.flash(_('Updated mercurial settings'),
212 category='success')
212 category='success')
213
213
214 except:
214 except:
215 log.error(traceback.format_exc())
215 log.error(traceback.format_exc())
216 h.flash(_('error occurred during updating '
216 h.flash(_('error occurred during updating '
217 'application settings'), category='error')
217 'application settings'), category='error')
218
218
219 self.sa.rollback()
219 self.sa.rollback()
220
220
221 except formencode.Invalid, errors:
221 except formencode.Invalid, errors:
222 return htmlfill.render(
222 return htmlfill.render(
223 render('admin/settings/settings.html'),
223 render('admin/settings/settings.html'),
224 defaults=errors.value,
224 defaults=errors.value,
225 errors=errors.error_dict or {},
225 errors=errors.error_dict or {},
226 prefix_error=False,
226 prefix_error=False,
227 encoding="UTF-8")
227 encoding="UTF-8")
228
228
229
229
230 if setting_id == 'hooks':
230 if setting_id == 'hooks':
231 ui_key = request.POST.get('new_hook_ui_key')
231 ui_key = request.POST.get('new_hook_ui_key')
232 ui_value = request.POST.get('new_hook_ui_value')
232 ui_value = request.POST.get('new_hook_ui_value')
233 try:
233 try:
234
234
235 if ui_value and ui_key:
235 if ui_value and ui_key:
236 RhodeCodeUi.create_or_update_hook(ui_key, ui_value)
236 RhodeCodeUi.create_or_update_hook(ui_key, ui_value)
237 h.flash(_('Added new hook'),
237 h.flash(_('Added new hook'),
238 category='success')
238 category='success')
239
239
240 # check for edits
240 # check for edits
241 update = False
241 update = False
242 _d = request.POST.dict_of_lists()
242 _d = request.POST.dict_of_lists()
243 for k, v in zip(_d.get('hook_ui_key',[]), _d.get('hook_ui_value_new',[])):
243 for k, v in zip(_d.get('hook_ui_key',[]), _d.get('hook_ui_value_new',[])):
244 RhodeCodeUi.create_or_update_hook(k, v)
244 RhodeCodeUi.create_or_update_hook(k, v)
245 update = True
245 update = True
246
246
247 if update:
247 if update:
248 h.flash(_('Updated hooks'), category='success')
248 h.flash(_('Updated hooks'), category='success')
249
249
250 except:
250 except:
251 log.error(traceback.format_exc())
251 log.error(traceback.format_exc())
252 h.flash(_('error occurred during hook creation'),
252 h.flash(_('error occurred during hook creation'),
253 category='error')
253 category='error')
254
254
255 return redirect(url('admin_edit_setting', setting_id='hooks'))
255 return redirect(url('admin_edit_setting', setting_id='hooks'))
256
256
257 return redirect(url('admin_settings'))
257 return redirect(url('admin_settings'))
258
258
259 @HasPermissionAllDecorator('hg.admin')
259 @HasPermissionAllDecorator('hg.admin')
260 def delete(self, setting_id):
260 def delete(self, setting_id):
261 """DELETE /admin/settings/setting_id: Delete an existing item"""
261 """DELETE /admin/settings/setting_id: Delete an existing item"""
262 # Forms posted to this method should contain a hidden field:
262 # Forms posted to this method should contain a hidden field:
263 # <input type="hidden" name="_method" value="DELETE" />
263 # <input type="hidden" name="_method" value="DELETE" />
264 # Or using helpers:
264 # Or using helpers:
265 # h.form(url('admin_setting', setting_id=ID),
265 # h.form(url('admin_setting', setting_id=ID),
266 # method='delete')
266 # method='delete')
267 # url('admin_setting', setting_id=ID)
267 # url('admin_setting', setting_id=ID)
268 if setting_id == 'hooks':
268 if setting_id == 'hooks':
269 hook_id = request.POST.get('hook_id')
269 hook_id = request.POST.get('hook_id')
270 RhodeCodeUi.delete(hook_id)
270 RhodeCodeUi.delete(hook_id)
271
271
272
272
273 @HasPermissionAllDecorator('hg.admin')
273 @HasPermissionAllDecorator('hg.admin')
274 def show(self, setting_id, format='html'):
274 def show(self, setting_id, format='html'):
275 """
275 """
276 GET /admin/settings/setting_id: Show a specific item"""
276 GET /admin/settings/setting_id: Show a specific item"""
277 # url('admin_setting', setting_id=ID)
277 # url('admin_setting', setting_id=ID)
278
278
279 @HasPermissionAllDecorator('hg.admin')
279 @HasPermissionAllDecorator('hg.admin')
280 def edit(self, setting_id, format='html'):
280 def edit(self, setting_id, format='html'):
281 """
281 """
282 GET /admin/settings/setting_id/edit: Form to
282 GET /admin/settings/setting_id/edit: Form to
283 edit an existing item"""
283 edit an existing item"""
284 # url('admin_edit_setting', setting_id=ID)
284 # url('admin_edit_setting', setting_id=ID)
285
285
286 c.hooks = RhodeCodeUi.get_builtin_hooks()
286 c.hooks = RhodeCodeUi.get_builtin_hooks()
287 c.custom_hooks = RhodeCodeUi.get_custom_hooks()
287 c.custom_hooks = RhodeCodeUi.get_custom_hooks()
288
288
289 return htmlfill.render(
289 return htmlfill.render(
290 render('admin/settings/hooks.html'),
290 render('admin/settings/hooks.html'),
291 defaults={},
291 defaults={},
292 encoding="UTF-8",
292 encoding="UTF-8",
293 force_defaults=False
293 force_defaults=False
294 )
294 )
295
295
296 @NotAnonymous()
296 @NotAnonymous()
297 def my_account(self):
297 def my_account(self):
298 """
298 """
299 GET /_admin/my_account Displays info about my account
299 GET /_admin/my_account Displays info about my account
300 """
300 """
301 # url('admin_settings_my_account')
301 # url('admin_settings_my_account')
302
302
303 c.user = User.get(self.rhodecode_user.user_id)
303 c.user = User.get(self.rhodecode_user.user_id)
304 all_repos = self.sa.query(Repository)\
304 all_repos = self.sa.query(Repository)\
305 .filter(Repository.user_id == c.user.user_id)\
305 .filter(Repository.user_id == c.user.user_id)\
306 .order_by(func.lower(Repository.repo_name)).all()
306 .order_by(func.lower(Repository.repo_name)).all()
307
307
308 c.user_repos = ScmModel().get_repos(all_repos)
308 c.user_repos = ScmModel().get_repos(all_repos)
309
309
310 if c.user.username == 'default':
310 if c.user.username == 'default':
311 h.flash(_("You can't edit this user since it's"
311 h.flash(_("You can't edit this user since it's"
312 " crucial for entire application"), category='warning')
312 " crucial for entire application"), category='warning')
313 return redirect(url('users'))
313 return redirect(url('users'))
314
314
315 defaults = c.user.get_dict()
315 defaults = c.user.get_dict()
316 return htmlfill.render(
316 return htmlfill.render(
317 render('admin/users/user_edit_my_account.html'),
317 render('admin/users/user_edit_my_account.html'),
318 defaults=defaults,
318 defaults=defaults,
319 encoding="UTF-8",
319 encoding="UTF-8",
320 force_defaults=False
320 force_defaults=False
321 )
321 )
322
322
323 def my_account_update(self):
323 def my_account_update(self):
324 """PUT /_admin/my_account_update: Update an existing item"""
324 """PUT /_admin/my_account_update: Update an existing item"""
325 # Forms posted to this method should contain a hidden field:
325 # Forms posted to this method should contain a hidden field:
326 # <input type="hidden" name="_method" value="PUT" />
326 # <input type="hidden" name="_method" value="PUT" />
327 # Or using helpers:
327 # Or using helpers:
328 # h.form(url('admin_settings_my_account_update'),
328 # h.form(url('admin_settings_my_account_update'),
329 # method='put')
329 # method='put')
330 # url('admin_settings_my_account_update', id=ID)
330 # url('admin_settings_my_account_update', id=ID)
331 user_model = UserModel()
331 user_model = UserModel()
332 uid = self.rhodecode_user.user_id
332 uid = self.rhodecode_user.user_id
333 _form = UserForm(edit=True,
333 _form = UserForm(edit=True,
334 old_data={'user_id': uid,
334 old_data={'user_id': uid,
335 'email': self.rhodecode_user.email})()
335 'email': self.rhodecode_user.email})()
336 form_result = {}
336 form_result = {}
337 try:
337 try:
338 form_result = _form.to_python(dict(request.POST))
338 form_result = _form.to_python(dict(request.POST))
339 user_model.update_my_account(uid, form_result)
339 user_model.update_my_account(uid, form_result)
340 h.flash(_('Your account was updated successfully'),
340 h.flash(_('Your account was updated successfully'),
341 category='success')
341 category='success')
342
342
343 except formencode.Invalid, errors:
343 except formencode.Invalid, errors:
344 c.user = User.get(self.rhodecode_user.user_id)
344 c.user = User.get(self.rhodecode_user.user_id)
345 all_repos = self.sa.query(Repository)\
345 all_repos = self.sa.query(Repository)\
346 .filter(Repository.user_id == c.user.user_id)\
346 .filter(Repository.user_id == c.user.user_id)\
347 .order_by(func.lower(Repository.repo_name))\
347 .order_by(func.lower(Repository.repo_name))\
348 .all()
348 .all()
349 c.user_repos = ScmModel().get_repos(all_repos)
349 c.user_repos = ScmModel().get_repos(all_repos)
350
350
351 return htmlfill.render(
351 return htmlfill.render(
352 render('admin/users/user_edit_my_account.html'),
352 render('admin/users/user_edit_my_account.html'),
353 defaults=errors.value,
353 defaults=errors.value,
354 errors=errors.error_dict or {},
354 errors=errors.error_dict or {},
355 prefix_error=False,
355 prefix_error=False,
356 encoding="UTF-8")
356 encoding="UTF-8")
357 except Exception:
357 except Exception:
358 log.error(traceback.format_exc())
358 log.error(traceback.format_exc())
359 h.flash(_('error occurred during update of user %s') \
359 h.flash(_('error occurred during update of user %s') \
360 % form_result.get('username'), category='error')
360 % form_result.get('username'), category='error')
361
361
362 return redirect(url('my_account'))
362 return redirect(url('my_account'))
363
363
364 @NotAnonymous()
364 @NotAnonymous()
365 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
365 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
366 def create_repository(self):
366 def create_repository(self):
367 """GET /_admin/create_repository: Form to create a new item"""
367 """GET /_admin/create_repository: Form to create a new item"""
368
368
369 c.repo_groups = [('', '')]
369 c.repo_groups = Group.groups_choices()
370 parents_link = lambda k: h.literal('&raquo;'.join(
371 map(lambda k: k.group_name,
372 k.parents + [k])
373 )
374 )
375
376 c.repo_groups.extend([(x.group_id, parents_link(x)) for \
377 x in self.sa.query(Group).all()])
378 c.repo_groups = sorted(c.repo_groups,
379 key=lambda t: t[1].split('&raquo;')[0])
380 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
370 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
381
371
382 new_repo = request.GET.get('repo', '')
372 new_repo = request.GET.get('repo', '')
383 c.new_repo = repo_name_slug(new_repo)
373 c.new_repo = repo_name_slug(new_repo)
384
374
385 return render('admin/repos/repo_add_create_repository.html')
375 return render('admin/repos/repo_add_create_repository.html')
386
376
387 def get_hg_ui_settings(self):
377 def get_hg_ui_settings(self):
388 ret = self.sa.query(RhodeCodeUi).all()
378 ret = self.sa.query(RhodeCodeUi).all()
389
379
390 if not ret:
380 if not ret:
391 raise Exception('Could not get application ui settings !')
381 raise Exception('Could not get application ui settings !')
392 settings = {}
382 settings = {}
393 for each in ret:
383 for each in ret:
394 k = each.ui_key
384 k = each.ui_key
395 v = each.ui_value
385 v = each.ui_value
396 if k == '/':
386 if k == '/':
397 k = 'root_path'
387 k = 'root_path'
398
388
399 if k.find('.') != -1:
389 if k.find('.') != -1:
400 k = k.replace('.', '_')
390 k = k.replace('.', '_')
401
391
402 if each.ui_section == 'hooks':
392 if each.ui_section == 'hooks':
403 v = each.ui_active
393 v = each.ui_active
404
394
405 settings[each.ui_section + '_' + k] = v
395 settings[each.ui_section + '_' + k] = v
406
396
407 return settings
397 return settings
@@ -1,168 +1,166
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.login
3 rhodecode.controllers.login
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Login controller for rhodeocode
6 Login controller for rhodeocode
7
7
8 :created_on: Apr 22, 2010
8 :created_on: Apr 22, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import logging
26 import logging
27 import formencode
27 import formencode
28
28
29 from formencode import htmlfill
29 from formencode import htmlfill
30
30
31 from pylons.i18n.translation import _
31 from pylons.i18n.translation import _
32 from pylons.controllers.util import abort, redirect
32 from pylons.controllers.util import abort, redirect
33 from pylons import request, response, session, tmpl_context as c, url
33 from pylons import request, response, session, tmpl_context as c, url
34
34
35 import rhodecode.lib.helpers as h
35 import rhodecode.lib.helpers as h
36 from rhodecode.lib.auth import AuthUser, HasPermissionAnyDecorator
36 from rhodecode.lib.auth import AuthUser, HasPermissionAnyDecorator
37 from rhodecode.lib.base import BaseController, render
37 from rhodecode.lib.base import BaseController, render
38 from rhodecode.model.db import User
38 from rhodecode.model.db import User
39 from rhodecode.model.forms import LoginForm, RegisterForm, PasswordResetForm
39 from rhodecode.model.forms import LoginForm, RegisterForm, PasswordResetForm
40 from rhodecode.model.user import UserModel
40 from rhodecode.model.user import UserModel
41
41
42
42
43 log = logging.getLogger(__name__)
43 log = logging.getLogger(__name__)
44
44
45
45
46 class LoginController(BaseController):
46 class LoginController(BaseController):
47
47
48 def __before__(self):
48 def __before__(self):
49 super(LoginController, self).__before__()
49 super(LoginController, self).__before__()
50
50
51 def index(self):
51 def index(self):
52 #redirect if already logged in
52 #redirect if already logged in
53 c.came_from = request.GET.get('came_from', None)
53 c.came_from = request.GET.get('came_from', None)
54
54
55 if self.rhodecode_user.is_authenticated \
55 if self.rhodecode_user.is_authenticated \
56 and self.rhodecode_user.username != 'default':
56 and self.rhodecode_user.username != 'default':
57
57
58 return redirect(url('home'))
58 return redirect(url('home'))
59
59
60 if request.POST:
60 if request.POST:
61 #import Login Form validator class
61 #import Login Form validator class
62 login_form = LoginForm()
62 login_form = LoginForm()
63 try:
63 try:
64 c.form_result = login_form.to_python(dict(request.POST))
64 c.form_result = login_form.to_python(dict(request.POST))
65 #form checks for username/password, now we're authenticated
65 #form checks for username/password, now we're authenticated
66 username = c.form_result['username']
66 username = c.form_result['username']
67 user = User.by_username(username,
67 user = User.get_by_username(username, case_insensitive=True)
68 case_insensitive=True)
69 auth_user = AuthUser(user.user_id)
68 auth_user = AuthUser(user.user_id)
70 auth_user.set_authenticated()
69 auth_user.set_authenticated()
71 session['rhodecode_user'] = auth_user
70 session['rhodecode_user'] = auth_user
72 session.save()
71 session.save()
73
72
74 log.info('user %s is now authenticated and stored in session',
73 log.info('user %s is now authenticated and stored in session',
75 username)
74 username)
76 user.update_lastlogin()
75 user.update_lastlogin()
77
76
78 if c.came_from:
77 if c.came_from:
79 return redirect(c.came_from)
78 return redirect(c.came_from)
80 else:
79 else:
81 return redirect(url('home'))
80 return redirect(url('home'))
82
81
83 except formencode.Invalid, errors:
82 except formencode.Invalid, errors:
84 return htmlfill.render(
83 return htmlfill.render(
85 render('/login.html'),
84 render('/login.html'),
86 defaults=errors.value,
85 defaults=errors.value,
87 errors=errors.error_dict or {},
86 errors=errors.error_dict or {},
88 prefix_error=False,
87 prefix_error=False,
89 encoding="UTF-8")
88 encoding="UTF-8")
90
89
91 return render('/login.html')
90 return render('/login.html')
92
91
93 @HasPermissionAnyDecorator('hg.admin', 'hg.register.auto_activate',
92 @HasPermissionAnyDecorator('hg.admin', 'hg.register.auto_activate',
94 'hg.register.manual_activate')
93 'hg.register.manual_activate')
95 def register(self):
94 def register(self):
96 user_model = UserModel()
95 user_model = UserModel()
97 c.auto_active = False
96 c.auto_active = False
98 for perm in user_model.get_by_username('default',
97 for perm in User.get_by_username('default').user_perms:
99 cache=False).user_perms:
100 if perm.permission.permission_name == 'hg.register.auto_activate':
98 if perm.permission.permission_name == 'hg.register.auto_activate':
101 c.auto_active = True
99 c.auto_active = True
102 break
100 break
103
101
104 if request.POST:
102 if request.POST:
105
103
106 register_form = RegisterForm()()
104 register_form = RegisterForm()()
107 try:
105 try:
108 form_result = register_form.to_python(dict(request.POST))
106 form_result = register_form.to_python(dict(request.POST))
109 form_result['active'] = c.auto_active
107 form_result['active'] = c.auto_active
110 user_model.create_registration(form_result)
108 user_model.create_registration(form_result)
111 h.flash(_('You have successfully registered into rhodecode'),
109 h.flash(_('You have successfully registered into rhodecode'),
112 category='success')
110 category='success')
113 return redirect(url('login_home'))
111 return redirect(url('login_home'))
114
112
115 except formencode.Invalid, errors:
113 except formencode.Invalid, errors:
116 return htmlfill.render(
114 return htmlfill.render(
117 render('/register.html'),
115 render('/register.html'),
118 defaults=errors.value,
116 defaults=errors.value,
119 errors=errors.error_dict or {},
117 errors=errors.error_dict or {},
120 prefix_error=False,
118 prefix_error=False,
121 encoding="UTF-8")
119 encoding="UTF-8")
122
120
123 return render('/register.html')
121 return render('/register.html')
124
122
125 def password_reset(self):
123 def password_reset(self):
126 user_model = UserModel()
124 user_model = UserModel()
127 if request.POST:
125 if request.POST:
128
126
129 password_reset_form = PasswordResetForm()()
127 password_reset_form = PasswordResetForm()()
130 try:
128 try:
131 form_result = password_reset_form.to_python(dict(request.POST))
129 form_result = password_reset_form.to_python(dict(request.POST))
132 user_model.reset_password_link(form_result)
130 user_model.reset_password_link(form_result)
133 h.flash(_('Your password reset link was sent'),
131 h.flash(_('Your password reset link was sent'),
134 category='success')
132 category='success')
135 return redirect(url('login_home'))
133 return redirect(url('login_home'))
136
134
137 except formencode.Invalid, errors:
135 except formencode.Invalid, errors:
138 return htmlfill.render(
136 return htmlfill.render(
139 render('/password_reset.html'),
137 render('/password_reset.html'),
140 defaults=errors.value,
138 defaults=errors.value,
141 errors=errors.error_dict or {},
139 errors=errors.error_dict or {},
142 prefix_error=False,
140 prefix_error=False,
143 encoding="UTF-8")
141 encoding="UTF-8")
144
142
145 return render('/password_reset.html')
143 return render('/password_reset.html')
146
144
147 def password_reset_confirmation(self):
145 def password_reset_confirmation(self):
148
146
149 if request.GET and request.GET.get('key'):
147 if request.GET and request.GET.get('key'):
150 try:
148 try:
151 user_model = UserModel()
149 user_model = UserModel()
152 user = User.get_by_api_key(request.GET.get('key'))
150 user = User.get_by_api_key(request.GET.get('key'))
153 data = dict(email=user.email)
151 data = dict(email=user.email)
154 user_model.reset_password(data)
152 user_model.reset_password(data)
155 h.flash(_('Your password reset was successful, '
153 h.flash(_('Your password reset was successful, '
156 'new password has been sent to your email'),
154 'new password has been sent to your email'),
157 category='success')
155 category='success')
158 except Exception, e:
156 except Exception, e:
159 log.error(e)
157 log.error(e)
160 return redirect(url('reset_password'))
158 return redirect(url('reset_password'))
161
159
162 return redirect(url('login_home'))
160 return redirect(url('login_home'))
163
161
164 def logout(self):
162 def logout(self):
165 del session['rhodecode_user']
163 del session['rhodecode_user']
166 session.save()
164 session.save()
167 log.info('Logging out and setting user as Empty')
165 log.info('Logging out and setting user as Empty')
168 redirect(url('home'))
166 redirect(url('home'))
@@ -1,381 +1,404
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.__init__
3 rhodecode.lib.__init__
4 ~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Some simple helper functions
6 Some simple helper functions
7
7
8 :created_on: Jan 5, 2011
8 :created_on: Jan 5, 2011
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import os
27
26 def __get_lem():
28 def __get_lem():
27 from pygments import lexers
29 from pygments import lexers
28 from string import lower
30 from string import lower
29 from collections import defaultdict
31 from collections import defaultdict
30
32
31 d = defaultdict(lambda: [])
33 d = defaultdict(lambda: [])
32
34
33 def __clean(s):
35 def __clean(s):
34 s = s.lstrip('*')
36 s = s.lstrip('*')
35 s = s.lstrip('.')
37 s = s.lstrip('.')
36
38
37 if s.find('[') != -1:
39 if s.find('[') != -1:
38 exts = []
40 exts = []
39 start, stop = s.find('['), s.find(']')
41 start, stop = s.find('['), s.find(']')
40
42
41 for suffix in s[start + 1:stop]:
43 for suffix in s[start + 1:stop]:
42 exts.append(s[:s.find('[')] + suffix)
44 exts.append(s[:s.find('[')] + suffix)
43 return map(lower, exts)
45 return map(lower, exts)
44 else:
46 else:
45 return map(lower, [s])
47 return map(lower, [s])
46
48
47 for lx, t in sorted(lexers.LEXERS.items()):
49 for lx, t in sorted(lexers.LEXERS.items()):
48 m = map(__clean, t[-2])
50 m = map(__clean, t[-2])
49 if m:
51 if m:
50 m = reduce(lambda x, y: x + y, m)
52 m = reduce(lambda x, y: x + y, m)
51 for ext in m:
53 for ext in m:
52 desc = lx.replace('Lexer', '')
54 desc = lx.replace('Lexer', '')
53 d[ext].append(desc)
55 d[ext].append(desc)
54
56
55 return dict(d)
57 return dict(d)
56
58
57 # language map is also used by whoosh indexer, which for those specified
59 # language map is also used by whoosh indexer, which for those specified
58 # extensions will index it's content
60 # extensions will index it's content
59 LANGUAGES_EXTENSIONS_MAP = __get_lem()
61 LANGUAGES_EXTENSIONS_MAP = __get_lem()
60
62
61 # Additional mappings that are not present in the pygments lexers
63 # Additional mappings that are not present in the pygments lexers
62 # NOTE: that this will overide any mappings in LANGUAGES_EXTENSIONS_MAP
64 # NOTE: that this will overide any mappings in LANGUAGES_EXTENSIONS_MAP
63 ADDITIONAL_MAPPINGS = {'xaml': 'XAML'}
65 ADDITIONAL_MAPPINGS = {'xaml': 'XAML'}
64
66
65 LANGUAGES_EXTENSIONS_MAP.update(ADDITIONAL_MAPPINGS)
67 LANGUAGES_EXTENSIONS_MAP.update(ADDITIONAL_MAPPINGS)
66
68
67
69
68 def str2bool(_str):
70 def str2bool(_str):
69 """
71 """
70 returs True/False value from given string, it tries to translate the
72 returs True/False value from given string, it tries to translate the
71 string into boolean
73 string into boolean
72
74
73 :param _str: string value to translate into boolean
75 :param _str: string value to translate into boolean
74 :rtype: boolean
76 :rtype: boolean
75 :returns: boolean from given string
77 :returns: boolean from given string
76 """
78 """
77 if _str is None:
79 if _str is None:
78 return False
80 return False
79 if _str in (True, False):
81 if _str in (True, False):
80 return _str
82 return _str
81 _str = str(_str).strip().lower()
83 _str = str(_str).strip().lower()
82 return _str in ('t', 'true', 'y', 'yes', 'on', '1')
84 return _str in ('t', 'true', 'y', 'yes', 'on', '1')
83
85
84
86
85 def convert_line_endings(line, mode):
87 def convert_line_endings(line, mode):
86 """
88 """
87 Converts a given line "line end" accordingly to given mode
89 Converts a given line "line end" accordingly to given mode
88
90
89 Available modes are::
91 Available modes are::
90 0 - Unix
92 0 - Unix
91 1 - Mac
93 1 - Mac
92 2 - DOS
94 2 - DOS
93
95
94 :param line: given line to convert
96 :param line: given line to convert
95 :param mode: mode to convert to
97 :param mode: mode to convert to
96 :rtype: str
98 :rtype: str
97 :return: converted line according to mode
99 :return: converted line according to mode
98 """
100 """
99 from string import replace
101 from string import replace
100
102
101 if mode == 0:
103 if mode == 0:
102 line = replace(line, '\r\n', '\n')
104 line = replace(line, '\r\n', '\n')
103 line = replace(line, '\r', '\n')
105 line = replace(line, '\r', '\n')
104 elif mode == 1:
106 elif mode == 1:
105 line = replace(line, '\r\n', '\r')
107 line = replace(line, '\r\n', '\r')
106 line = replace(line, '\n', '\r')
108 line = replace(line, '\n', '\r')
107 elif mode == 2:
109 elif mode == 2:
108 import re
110 import re
109 line = re.sub("\r(?!\n)|(?<!\r)\n", "\r\n", line)
111 line = re.sub("\r(?!\n)|(?<!\r)\n", "\r\n", line)
110 return line
112 return line
111
113
112
114
113 def detect_mode(line, default):
115 def detect_mode(line, default):
114 """
116 """
115 Detects line break for given line, if line break couldn't be found
117 Detects line break for given line, if line break couldn't be found
116 given default value is returned
118 given default value is returned
117
119
118 :param line: str line
120 :param line: str line
119 :param default: default
121 :param default: default
120 :rtype: int
122 :rtype: int
121 :return: value of line end on of 0 - Unix, 1 - Mac, 2 - DOS
123 :return: value of line end on of 0 - Unix, 1 - Mac, 2 - DOS
122 """
124 """
123 if line.endswith('\r\n'):
125 if line.endswith('\r\n'):
124 return 2
126 return 2
125 elif line.endswith('\n'):
127 elif line.endswith('\n'):
126 return 0
128 return 0
127 elif line.endswith('\r'):
129 elif line.endswith('\r'):
128 return 1
130 return 1
129 else:
131 else:
130 return default
132 return default
131
133
132
134
133 def generate_api_key(username, salt=None):
135 def generate_api_key(username, salt=None):
134 """
136 """
135 Generates unique API key for given username, if salt is not given
137 Generates unique API key for given username, if salt is not given
136 it'll be generated from some random string
138 it'll be generated from some random string
137
139
138 :param username: username as string
140 :param username: username as string
139 :param salt: salt to hash generate KEY
141 :param salt: salt to hash generate KEY
140 :rtype: str
142 :rtype: str
141 :returns: sha1 hash from username+salt
143 :returns: sha1 hash from username+salt
142 """
144 """
143 from tempfile import _RandomNameSequence
145 from tempfile import _RandomNameSequence
144 import hashlib
146 import hashlib
145
147
146 if salt is None:
148 if salt is None:
147 salt = _RandomNameSequence().next()
149 salt = _RandomNameSequence().next()
148
150
149 return hashlib.sha1(username + salt).hexdigest()
151 return hashlib.sha1(username + salt).hexdigest()
150
152
151
153
152 def safe_unicode(str_, from_encoding='utf8'):
154 def safe_unicode(str_, from_encoding='utf8'):
153 """
155 """
154 safe unicode function. Does few trick to turn str_ into unicode
156 safe unicode function. Does few trick to turn str_ into unicode
155
157
156 In case of UnicodeDecode error we try to return it with encoding detected
158 In case of UnicodeDecode error we try to return it with encoding detected
157 by chardet library if it fails fallback to unicode with errors replaced
159 by chardet library if it fails fallback to unicode with errors replaced
158
160
159 :param str_: string to decode
161 :param str_: string to decode
160 :rtype: unicode
162 :rtype: unicode
161 :returns: unicode object
163 :returns: unicode object
162 """
164 """
163 if isinstance(str_, unicode):
165 if isinstance(str_, unicode):
164 return str_
166 return str_
165
167
166 try:
168 try:
167 return unicode(str_)
169 return unicode(str_)
168 except UnicodeDecodeError:
170 except UnicodeDecodeError:
169 pass
171 pass
170
172
171 try:
173 try:
172 return unicode(str_, from_encoding)
174 return unicode(str_, from_encoding)
173 except UnicodeDecodeError:
175 except UnicodeDecodeError:
174 pass
176 pass
175
177
176 try:
178 try:
177 import chardet
179 import chardet
178 encoding = chardet.detect(str_)['encoding']
180 encoding = chardet.detect(str_)['encoding']
179 if encoding is None:
181 if encoding is None:
180 raise Exception()
182 raise Exception()
181 return str_.decode(encoding)
183 return str_.decode(encoding)
182 except (ImportError, UnicodeDecodeError, Exception):
184 except (ImportError, UnicodeDecodeError, Exception):
183 return unicode(str_, from_encoding, 'replace')
185 return unicode(str_, from_encoding, 'replace')
184
186
185 def safe_str(unicode_, to_encoding='utf8'):
187 def safe_str(unicode_, to_encoding='utf8'):
186 """
188 """
187 safe str function. Does few trick to turn unicode_ into string
189 safe str function. Does few trick to turn unicode_ into string
188
190
189 In case of UnicodeEncodeError we try to return it with encoding detected
191 In case of UnicodeEncodeError we try to return it with encoding detected
190 by chardet library if it fails fallback to string with errors replaced
192 by chardet library if it fails fallback to string with errors replaced
191
193
192 :param unicode_: unicode to encode
194 :param unicode_: unicode to encode
193 :rtype: str
195 :rtype: str
194 :returns: str object
196 :returns: str object
195 """
197 """
196
198
197 if isinstance(unicode_, str):
199 if isinstance(unicode_, str):
198 return unicode_
200 return unicode_
199
201
200 try:
202 try:
201 return unicode_.encode(to_encoding)
203 return unicode_.encode(to_encoding)
202 except UnicodeEncodeError:
204 except UnicodeEncodeError:
203 pass
205 pass
204
206
205 try:
207 try:
206 import chardet
208 import chardet
207 encoding = chardet.detect(unicode_)['encoding']
209 encoding = chardet.detect(unicode_)['encoding']
208 print encoding
210 print encoding
209 if encoding is None:
211 if encoding is None:
210 raise UnicodeEncodeError()
212 raise UnicodeEncodeError()
211
213
212 return unicode_.encode(encoding)
214 return unicode_.encode(encoding)
213 except (ImportError, UnicodeEncodeError):
215 except (ImportError, UnicodeEncodeError):
214 return unicode_.encode(to_encoding, 'replace')
216 return unicode_.encode(to_encoding, 'replace')
215
217
216 return safe_str
218 return safe_str
217
219
218
220
219
221
220 def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs):
222 def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs):
221 """
223 """
222 Custom engine_from_config functions that makes sure we use NullPool for
224 Custom engine_from_config functions that makes sure we use NullPool for
223 file based sqlite databases. This prevents errors on sqlite. This only
225 file based sqlite databases. This prevents errors on sqlite. This only
224 applies to sqlalchemy versions < 0.7.0
226 applies to sqlalchemy versions < 0.7.0
225
227
226 """
228 """
227 import sqlalchemy
229 import sqlalchemy
228 from sqlalchemy import engine_from_config as efc
230 from sqlalchemy import engine_from_config as efc
229 import logging
231 import logging
230
232
231 if int(sqlalchemy.__version__.split('.')[1]) < 7:
233 if int(sqlalchemy.__version__.split('.')[1]) < 7:
232
234
233 # This solution should work for sqlalchemy < 0.7.0, and should use
235 # This solution should work for sqlalchemy < 0.7.0, and should use
234 # proxy=TimerProxy() for execution time profiling
236 # proxy=TimerProxy() for execution time profiling
235
237
236 from sqlalchemy.pool import NullPool
238 from sqlalchemy.pool import NullPool
237 url = configuration[prefix + 'url']
239 url = configuration[prefix + 'url']
238
240
239 if url.startswith('sqlite'):
241 if url.startswith('sqlite'):
240 kwargs.update({'poolclass': NullPool})
242 kwargs.update({'poolclass': NullPool})
241 return efc(configuration, prefix, **kwargs)
243 return efc(configuration, prefix, **kwargs)
242 else:
244 else:
243 import time
245 import time
244 from sqlalchemy import event
246 from sqlalchemy import event
245 from sqlalchemy.engine import Engine
247 from sqlalchemy.engine import Engine
246
248
247 log = logging.getLogger('sqlalchemy.engine')
249 log = logging.getLogger('sqlalchemy.engine')
248 BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = xrange(30, 38)
250 BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = xrange(30, 38)
249 engine = efc(configuration, prefix, **kwargs)
251 engine = efc(configuration, prefix, **kwargs)
250
252
251 def color_sql(sql):
253 def color_sql(sql):
252 COLOR_SEQ = "\033[1;%dm"
254 COLOR_SEQ = "\033[1;%dm"
253 COLOR_SQL = YELLOW
255 COLOR_SQL = YELLOW
254 normal = '\x1b[0m'
256 normal = '\x1b[0m'
255 return ''.join([COLOR_SEQ % COLOR_SQL, sql, normal])
257 return ''.join([COLOR_SEQ % COLOR_SQL, sql, normal])
256
258
257 if configuration['debug']:
259 if configuration['debug']:
258 #attach events only for debug configuration
260 #attach events only for debug configuration
259
261
260 def before_cursor_execute(conn, cursor, statement,
262 def before_cursor_execute(conn, cursor, statement,
261 parameters, context, executemany):
263 parameters, context, executemany):
262 context._query_start_time = time.time()
264 context._query_start_time = time.time()
263 log.info(color_sql(">>>>> STARTING QUERY >>>>>"))
265 log.info(color_sql(">>>>> STARTING QUERY >>>>>"))
264
266
265
267
266 def after_cursor_execute(conn, cursor, statement,
268 def after_cursor_execute(conn, cursor, statement,
267 parameters, context, executemany):
269 parameters, context, executemany):
268 total = time.time() - context._query_start_time
270 total = time.time() - context._query_start_time
269 log.info(color_sql("<<<<< TOTAL TIME: %f <<<<<" % total))
271 log.info(color_sql("<<<<< TOTAL TIME: %f <<<<<" % total))
270
272
271 event.listen(engine, "before_cursor_execute",
273 event.listen(engine, "before_cursor_execute",
272 before_cursor_execute)
274 before_cursor_execute)
273 event.listen(engine, "after_cursor_execute",
275 event.listen(engine, "after_cursor_execute",
274 after_cursor_execute)
276 after_cursor_execute)
275
277
276 return engine
278 return engine
277
279
278
280
279 def age(curdate):
281 def age(curdate):
280 """
282 """
281 turns a datetime into an age string.
283 turns a datetime into an age string.
282
284
283 :param curdate: datetime object
285 :param curdate: datetime object
284 :rtype: unicode
286 :rtype: unicode
285 :returns: unicode words describing age
287 :returns: unicode words describing age
286 """
288 """
287
289
288 from datetime import datetime
290 from datetime import datetime
289 from webhelpers.date import time_ago_in_words
291 from webhelpers.date import time_ago_in_words
290
292
291 _ = lambda s:s
293 _ = lambda s:s
292
294
293 if not curdate:
295 if not curdate:
294 return ''
296 return ''
295
297
296 agescales = [(_(u"year"), 3600 * 24 * 365),
298 agescales = [(_(u"year"), 3600 * 24 * 365),
297 (_(u"month"), 3600 * 24 * 30),
299 (_(u"month"), 3600 * 24 * 30),
298 (_(u"day"), 3600 * 24),
300 (_(u"day"), 3600 * 24),
299 (_(u"hour"), 3600),
301 (_(u"hour"), 3600),
300 (_(u"minute"), 60),
302 (_(u"minute"), 60),
301 (_(u"second"), 1), ]
303 (_(u"second"), 1), ]
302
304
303 age = datetime.now() - curdate
305 age = datetime.now() - curdate
304 age_seconds = (age.days * agescales[2][1]) + age.seconds
306 age_seconds = (age.days * agescales[2][1]) + age.seconds
305 pos = 1
307 pos = 1
306 for scale in agescales:
308 for scale in agescales:
307 if scale[1] <= age_seconds:
309 if scale[1] <= age_seconds:
308 if pos == 6:pos = 5
310 if pos == 6:pos = 5
309 return '%s %s' % (time_ago_in_words(curdate,
311 return '%s %s' % (time_ago_in_words(curdate,
310 agescales[pos][0]), _('ago'))
312 agescales[pos][0]), _('ago'))
311 pos += 1
313 pos += 1
312
314
313 return _(u'just now')
315 return _(u'just now')
314
316
315
317
316 def uri_filter(uri):
318 def uri_filter(uri):
317 """
319 """
318 Removes user:password from given url string
320 Removes user:password from given url string
319
321
320 :param uri:
322 :param uri:
321 :rtype: unicode
323 :rtype: unicode
322 :returns: filtered list of strings
324 :returns: filtered list of strings
323 """
325 """
324 if not uri:
326 if not uri:
325 return ''
327 return ''
326
328
327 proto = ''
329 proto = ''
328
330
329 for pat in ('https://', 'http://'):
331 for pat in ('https://', 'http://'):
330 if uri.startswith(pat):
332 if uri.startswith(pat):
331 uri = uri[len(pat):]
333 uri = uri[len(pat):]
332 proto = pat
334 proto = pat
333 break
335 break
334
336
335 # remove passwords and username
337 # remove passwords and username
336 uri = uri[uri.find('@') + 1:]
338 uri = uri[uri.find('@') + 1:]
337
339
338 # get the port
340 # get the port
339 cred_pos = uri.find(':')
341 cred_pos = uri.find(':')
340 if cred_pos == -1:
342 if cred_pos == -1:
341 host, port = uri, None
343 host, port = uri, None
342 else:
344 else:
343 host, port = uri[:cred_pos], uri[cred_pos + 1:]
345 host, port = uri[:cred_pos], uri[cred_pos + 1:]
344
346
345 return filter(None, [proto, host, port])
347 return filter(None, [proto, host, port])
346
348
347
349
348 def credentials_filter(uri):
350 def credentials_filter(uri):
349 """
351 """
350 Returns a url with removed credentials
352 Returns a url with removed credentials
351
353
352 :param uri:
354 :param uri:
353 """
355 """
354
356
355 uri = uri_filter(uri)
357 uri = uri_filter(uri)
356 #check if we have port
358 #check if we have port
357 if len(uri) > 2 and uri[2]:
359 if len(uri) > 2 and uri[2]:
358 uri[2] = ':' + uri[2]
360 uri[2] = ':' + uri[2]
359
361
360 return ''.join(uri)
362 return ''.join(uri)
361
363
362 def get_changeset_safe(repo, rev):
364 def get_changeset_safe(repo, rev):
363 """
365 """
364 Safe version of get_changeset if this changeset doesn't exists for a
366 Safe version of get_changeset if this changeset doesn't exists for a
365 repo it returns a Dummy one instead
367 repo it returns a Dummy one instead
366
368
367 :param repo:
369 :param repo:
368 :param rev:
370 :param rev:
369 """
371 """
370 from vcs.backends.base import BaseRepository
372 from vcs.backends.base import BaseRepository
371 from vcs.exceptions import RepositoryError
373 from vcs.exceptions import RepositoryError
372 if not isinstance(repo, BaseRepository):
374 if not isinstance(repo, BaseRepository):
373 raise Exception('You must pass an Repository '
375 raise Exception('You must pass an Repository '
374 'object as first argument got %s', type(repo))
376 'object as first argument got %s', type(repo))
375
377
376 try:
378 try:
377 cs = repo.get_changeset(rev)
379 cs = repo.get_changeset(rev)
378 except RepositoryError:
380 except RepositoryError:
379 from rhodecode.lib.utils import EmptyChangeset
381 from rhodecode.lib.utils import EmptyChangeset
380 cs = EmptyChangeset(requested_revision=rev)
382 cs = EmptyChangeset(requested_revision=rev)
381 return cs
383 return cs
384
385
386 def get_current_revision():
387 """
388 Returns tuple of (number, id) from repository containing this package
389 or None if repository could not be found.
390 """
391
392 try:
393 from vcs import get_repo
394 from vcs.utils.helpers import get_scm
395 from vcs.exceptions import RepositoryError, VCSError
396 repopath = os.path.join(os.path.dirname(__file__), '..', '..')
397 scm = get_scm(repopath)[0]
398 repo = get_repo(path=repopath, alias=scm)
399 tip = repo.get_changeset()
400 return (tip.revision, tip.short_id)
401 except (ImportError, RepositoryError, VCSError), err:
402 print ("Cannot retrieve rhodecode's revision. Original error "
403 "was: %s" % err)
404 return None
@@ -1,613 +1,612
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.auth
3 rhodecode.lib.auth
4 ~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~
5
5
6 authentication and permission libraries
6 authentication and permission libraries
7
7
8 :created_on: Apr 4, 2010
8 :created_on: Apr 4, 2010
9 :copyright: (c) 2010 by marcink.
9 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :license: LICENSE_NAME, see LICENSE_FILE for more details.
10 :license: GPLv3, see COPYING for more details.
11 """
11 """
12 # This program is free software: you can redistribute it and/or modify
12 # This program is free software: you can redistribute it and/or modify
13 # it under the terms of the GNU General Public License as published by
13 # it under the terms of the GNU General Public License as published by
14 # the Free Software Foundation, either version 3 of the License, or
14 # the Free Software Foundation, either version 3 of the License, or
15 # (at your option) any later version.
15 # (at your option) any later version.
16 #
16 #
17 # This program is distributed in the hope that it will be useful,
17 # This program is distributed in the hope that it will be useful,
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # GNU General Public License for more details.
20 # GNU General Public License for more details.
21 #
21 #
22 # You should have received a copy of the GNU General Public License
22 # You should have received a copy of the GNU General Public License
23 # along with this program. If not, see <http://www.gnu.org/licenses/>.
23 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24
24
25 import random
25 import random
26 import logging
26 import logging
27 import traceback
27 import traceback
28 import hashlib
28 import hashlib
29
29
30 from tempfile import _RandomNameSequence
30 from tempfile import _RandomNameSequence
31 from decorator import decorator
31 from decorator import decorator
32
32
33 from pylons import config, session, url, request
33 from pylons import config, session, url, request
34 from pylons.controllers.util import abort, redirect
34 from pylons.controllers.util import abort, redirect
35 from pylons.i18n.translation import _
35 from pylons.i18n.translation import _
36
36
37 from rhodecode import __platform__, PLATFORM_WIN, PLATFORM_OTHERS
37 from rhodecode import __platform__, PLATFORM_WIN, PLATFORM_OTHERS
38
38
39 if __platform__ in PLATFORM_WIN:
39 if __platform__ in PLATFORM_WIN:
40 from hashlib import sha256
40 from hashlib import sha256
41 if __platform__ in PLATFORM_OTHERS:
41 if __platform__ in PLATFORM_OTHERS:
42 import bcrypt
42 import bcrypt
43
43
44 from rhodecode.lib import str2bool, safe_unicode
44 from rhodecode.lib import str2bool, safe_unicode
45 from rhodecode.lib.exceptions import LdapPasswordError, LdapUsernameError
45 from rhodecode.lib.exceptions import LdapPasswordError, LdapUsernameError
46 from rhodecode.lib.utils import get_repo_slug
46 from rhodecode.lib.utils import get_repo_slug
47 from rhodecode.lib.auth_ldap import AuthLdap
47 from rhodecode.lib.auth_ldap import AuthLdap
48
48
49 from rhodecode.model import meta
49 from rhodecode.model import meta
50 from rhodecode.model.user import UserModel
50 from rhodecode.model.user import UserModel
51 from rhodecode.model.db import Permission, RhodeCodeSettings
51 from rhodecode.model.db import Permission, RhodeCodeSettings, User
52
52
53 log = logging.getLogger(__name__)
53 log = logging.getLogger(__name__)
54
54
55
55
56 class PasswordGenerator(object):
56 class PasswordGenerator(object):
57 """This is a simple class for generating password from
57 """This is a simple class for generating password from
58 different sets of characters
58 different sets of characters
59 usage:
59 usage:
60 passwd_gen = PasswordGenerator()
60 passwd_gen = PasswordGenerator()
61 #print 8-letter password containing only big and small letters
61 #print 8-letter password containing only big and small letters
62 of alphabet
62 of alphabet
63 print passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
63 print passwd_gen.gen_password(8, passwd_gen.ALPHABETS_BIG_SMALL)
64 """
64 """
65 ALPHABETS_NUM = r'''1234567890'''
65 ALPHABETS_NUM = r'''1234567890'''
66 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
66 ALPHABETS_SMALL = r'''qwertyuiopasdfghjklzxcvbnm'''
67 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
67 ALPHABETS_BIG = r'''QWERTYUIOPASDFGHJKLZXCVBNM'''
68 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
68 ALPHABETS_SPECIAL = r'''`-=[]\;',./~!@#$%^&*()_+{}|:"<>?'''
69 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
69 ALPHABETS_FULL = ALPHABETS_BIG + ALPHABETS_SMALL \
70 + ALPHABETS_NUM + ALPHABETS_SPECIAL
70 + ALPHABETS_NUM + ALPHABETS_SPECIAL
71 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
71 ALPHABETS_ALPHANUM = ALPHABETS_BIG + ALPHABETS_SMALL + ALPHABETS_NUM
72 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
72 ALPHABETS_BIG_SMALL = ALPHABETS_BIG + ALPHABETS_SMALL
73 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
73 ALPHABETS_ALPHANUM_BIG = ALPHABETS_BIG + ALPHABETS_NUM
74 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
74 ALPHABETS_ALPHANUM_SMALL = ALPHABETS_SMALL + ALPHABETS_NUM
75
75
76 def __init__(self, passwd=''):
76 def __init__(self, passwd=''):
77 self.passwd = passwd
77 self.passwd = passwd
78
78
79 def gen_password(self, len, type):
79 def gen_password(self, len, type):
80 self.passwd = ''.join([random.choice(type) for _ in xrange(len)])
80 self.passwd = ''.join([random.choice(type) for _ in xrange(len)])
81 return self.passwd
81 return self.passwd
82
82
83
83
84 class RhodeCodeCrypto(object):
84 class RhodeCodeCrypto(object):
85
85
86 @classmethod
86 @classmethod
87 def hash_string(cls, str_):
87 def hash_string(cls, str_):
88 """
88 """
89 Cryptographic function used for password hashing based on pybcrypt
89 Cryptographic function used for password hashing based on pybcrypt
90 or pycrypto in windows
90 or pycrypto in windows
91
91
92 :param password: password to hash
92 :param password: password to hash
93 """
93 """
94 if __platform__ in PLATFORM_WIN:
94 if __platform__ in PLATFORM_WIN:
95 return sha256(str_).hexdigest()
95 return sha256(str_).hexdigest()
96 elif __platform__ in PLATFORM_OTHERS:
96 elif __platform__ in PLATFORM_OTHERS:
97 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
97 return bcrypt.hashpw(str_, bcrypt.gensalt(10))
98 else:
98 else:
99 raise Exception('Unknown or unsupported platform %s' \
99 raise Exception('Unknown or unsupported platform %s' \
100 % __platform__)
100 % __platform__)
101
101
102 @classmethod
102 @classmethod
103 def hash_check(cls, password, hashed):
103 def hash_check(cls, password, hashed):
104 """
104 """
105 Checks matching password with it's hashed value, runs different
105 Checks matching password with it's hashed value, runs different
106 implementation based on platform it runs on
106 implementation based on platform it runs on
107
107
108 :param password: password
108 :param password: password
109 :param hashed: password in hashed form
109 :param hashed: password in hashed form
110 """
110 """
111
111
112 if __platform__ in PLATFORM_WIN:
112 if __platform__ in PLATFORM_WIN:
113 return sha256(password).hexdigest() == hashed
113 return sha256(password).hexdigest() == hashed
114 elif __platform__ in PLATFORM_OTHERS:
114 elif __platform__ in PLATFORM_OTHERS:
115 return bcrypt.hashpw(password, hashed) == hashed
115 return bcrypt.hashpw(password, hashed) == hashed
116 else:
116 else:
117 raise Exception('Unknown or unsupported platform %s' \
117 raise Exception('Unknown or unsupported platform %s' \
118 % __platform__)
118 % __platform__)
119
119
120
120
121 def get_crypt_password(password):
121 def get_crypt_password(password):
122 return RhodeCodeCrypto.hash_string(password)
122 return RhodeCodeCrypto.hash_string(password)
123
123
124
124
125 def check_password(password, hashed):
125 def check_password(password, hashed):
126 return RhodeCodeCrypto.hash_check(password, hashed)
126 return RhodeCodeCrypto.hash_check(password, hashed)
127
127
128
128
129 def generate_api_key(username, salt=None):
129 def generate_api_key(username, salt=None):
130 if salt is None:
130 if salt is None:
131 salt = _RandomNameSequence().next()
131 salt = _RandomNameSequence().next()
132
132
133 return hashlib.sha1(username + salt).hexdigest()
133 return hashlib.sha1(username + salt).hexdigest()
134
134
135
135
136 def authfunc(environ, username, password):
136 def authfunc(environ, username, password):
137 """Dummy authentication function used in Mercurial/Git/ and access control,
137 """Dummy authentication function used in Mercurial/Git/ and access control,
138
138
139 :param environ: needed only for using in Basic auth
139 :param environ: needed only for using in Basic auth
140 """
140 """
141 return authenticate(username, password)
141 return authenticate(username, password)
142
142
143
143
144 def authenticate(username, password):
144 def authenticate(username, password):
145 """Authentication function used for access control,
145 """Authentication function used for access control,
146 firstly checks for db authentication then if ldap is enabled for ldap
146 firstly checks for db authentication then if ldap is enabled for ldap
147 authentication, also creates ldap user if not in database
147 authentication, also creates ldap user if not in database
148
148
149 :param username: username
149 :param username: username
150 :param password: password
150 :param password: password
151 """
151 """
152
152
153 user_model = UserModel()
153 user_model = UserModel()
154 user = user_model.get_by_username(username, cache=False)
154 user = User.get_by_username(username)
155
155
156 log.debug('Authenticating user using RhodeCode account')
156 log.debug('Authenticating user using RhodeCode account')
157 if user is not None and not user.ldap_dn:
157 if user is not None and not user.ldap_dn:
158 if user.active:
158 if user.active:
159 if user.username == 'default' and user.active:
159 if user.username == 'default' and user.active:
160 log.info('user %s authenticated correctly as anonymous user',
160 log.info('user %s authenticated correctly as anonymous user',
161 username)
161 username)
162 return True
162 return True
163
163
164 elif user.username == username and check_password(password,
164 elif user.username == username and check_password(password,
165 user.password):
165 user.password):
166 log.info('user %s authenticated correctly', username)
166 log.info('user %s authenticated correctly', username)
167 return True
167 return True
168 else:
168 else:
169 log.warning('user %s is disabled', username)
169 log.warning('user %s is disabled', username)
170
170
171 else:
171 else:
172 log.debug('Regular authentication failed')
172 log.debug('Regular authentication failed')
173 user_obj = user_model.get_by_username(username, cache=False,
173 user_obj = User.get_by_username(username, case_insensitive=True)
174 case_insensitive=True)
175
174
176 if user_obj is not None and not user_obj.ldap_dn:
175 if user_obj is not None and not user_obj.ldap_dn:
177 log.debug('this user already exists as non ldap')
176 log.debug('this user already exists as non ldap')
178 return False
177 return False
179
178
180 ldap_settings = RhodeCodeSettings.get_ldap_settings()
179 ldap_settings = RhodeCodeSettings.get_ldap_settings()
181 #======================================================================
180 #======================================================================
182 # FALLBACK TO LDAP AUTH IF ENABLE
181 # FALLBACK TO LDAP AUTH IF ENABLE
183 #======================================================================
182 #======================================================================
184 if str2bool(ldap_settings.get('ldap_active')):
183 if str2bool(ldap_settings.get('ldap_active')):
185 log.debug("Authenticating user using ldap")
184 log.debug("Authenticating user using ldap")
186 kwargs = {
185 kwargs = {
187 'server': ldap_settings.get('ldap_host', ''),
186 'server': ldap_settings.get('ldap_host', ''),
188 'base_dn': ldap_settings.get('ldap_base_dn', ''),
187 'base_dn': ldap_settings.get('ldap_base_dn', ''),
189 'port': ldap_settings.get('ldap_port'),
188 'port': ldap_settings.get('ldap_port'),
190 'bind_dn': ldap_settings.get('ldap_dn_user'),
189 'bind_dn': ldap_settings.get('ldap_dn_user'),
191 'bind_pass': ldap_settings.get('ldap_dn_pass'),
190 'bind_pass': ldap_settings.get('ldap_dn_pass'),
192 'tls_kind': ldap_settings.get('ldap_tls_kind'),
191 'tls_kind': ldap_settings.get('ldap_tls_kind'),
193 'tls_reqcert': ldap_settings.get('ldap_tls_reqcert'),
192 'tls_reqcert': ldap_settings.get('ldap_tls_reqcert'),
194 'ldap_filter': ldap_settings.get('ldap_filter'),
193 'ldap_filter': ldap_settings.get('ldap_filter'),
195 'search_scope': ldap_settings.get('ldap_search_scope'),
194 'search_scope': ldap_settings.get('ldap_search_scope'),
196 'attr_login': ldap_settings.get('ldap_attr_login'),
195 'attr_login': ldap_settings.get('ldap_attr_login'),
197 'ldap_version': 3,
196 'ldap_version': 3,
198 }
197 }
199 log.debug('Checking for ldap authentication')
198 log.debug('Checking for ldap authentication')
200 try:
199 try:
201 aldap = AuthLdap(**kwargs)
200 aldap = AuthLdap(**kwargs)
202 (user_dn, ldap_attrs) = aldap.authenticate_ldap(username,
201 (user_dn, ldap_attrs) = aldap.authenticate_ldap(username,
203 password)
202 password)
204 log.debug('Got ldap DN response %s', user_dn)
203 log.debug('Got ldap DN response %s', user_dn)
205
204
206 get_ldap_attr = lambda k: ldap_attrs.get(ldap_settings\
205 get_ldap_attr = lambda k: ldap_attrs.get(ldap_settings\
207 .get(k), [''])[0]
206 .get(k), [''])[0]
208
207
209 user_attrs = {
208 user_attrs = {
210 'name': safe_unicode(get_ldap_attr('ldap_attr_firstname')),
209 'name': safe_unicode(get_ldap_attr('ldap_attr_firstname')),
211 'lastname': safe_unicode(get_ldap_attr('ldap_attr_lastname')),
210 'lastname': safe_unicode(get_ldap_attr('ldap_attr_lastname')),
212 'email': get_ldap_attr('ldap_attr_email'),
211 'email': get_ldap_attr('ldap_attr_email'),
213 }
212 }
214
213
215 if user_model.create_ldap(username, password, user_dn,
214 if user_model.create_ldap(username, password, user_dn,
216 user_attrs):
215 user_attrs):
217 log.info('created new ldap user %s', username)
216 log.info('created new ldap user %s', username)
218
217
219 return True
218 return True
220 except (LdapUsernameError, LdapPasswordError,):
219 except (LdapUsernameError, LdapPasswordError,):
221 pass
220 pass
222 except (Exception,):
221 except (Exception,):
223 log.error(traceback.format_exc())
222 log.error(traceback.format_exc())
224 pass
223 pass
225 return False
224 return False
226
225
227
226
228 class AuthUser(object):
227 class AuthUser(object):
229 """
228 """
230 A simple object that handles all attributes of user in RhodeCode
229 A simple object that handles all attributes of user in RhodeCode
231
230
232 It does lookup based on API key,given user, or user present in session
231 It does lookup based on API key,given user, or user present in session
233 Then it fills all required information for such user. It also checks if
232 Then it fills all required information for such user. It also checks if
234 anonymous access is enabled and if so, it returns default user as logged
233 anonymous access is enabled and if so, it returns default user as logged
235 in
234 in
236 """
235 """
237
236
238 def __init__(self, user_id=None, api_key=None):
237 def __init__(self, user_id=None, api_key=None):
239
238
240 self.user_id = user_id
239 self.user_id = user_id
241 self.api_key = None
240 self.api_key = None
242
241
243 self.username = 'None'
242 self.username = 'None'
244 self.name = ''
243 self.name = ''
245 self.lastname = ''
244 self.lastname = ''
246 self.email = ''
245 self.email = ''
247 self.is_authenticated = False
246 self.is_authenticated = False
248 self.admin = False
247 self.admin = False
249 self.permissions = {}
248 self.permissions = {}
250 self._api_key = api_key
249 self._api_key = api_key
251 self.propagate_data()
250 self.propagate_data()
252
251
253 def propagate_data(self):
252 def propagate_data(self):
254 user_model = UserModel()
253 user_model = UserModel()
255 self.anonymous_user = user_model.get_by_username('default', cache=True)
254 self.anonymous_user = User.get_by_username('default')
256 if self._api_key and self._api_key != self.anonymous_user.api_key:
255 if self._api_key and self._api_key != self.anonymous_user.api_key:
257 #try go get user by api key
256 #try go get user by api key
258 log.debug('Auth User lookup by API KEY %s', self._api_key)
257 log.debug('Auth User lookup by API KEY %s', self._api_key)
259 user_model.fill_data(self, api_key=self._api_key)
258 user_model.fill_data(self, api_key=self._api_key)
260 else:
259 else:
261 log.debug('Auth User lookup by USER ID %s', self.user_id)
260 log.debug('Auth User lookup by USER ID %s', self.user_id)
262 if self.user_id is not None \
261 if self.user_id is not None \
263 and self.user_id != self.anonymous_user.user_id:
262 and self.user_id != self.anonymous_user.user_id:
264 user_model.fill_data(self, user_id=self.user_id)
263 user_model.fill_data(self, user_id=self.user_id)
265 else:
264 else:
266 if self.anonymous_user.active is True:
265 if self.anonymous_user.active is True:
267 user_model.fill_data(self,
266 user_model.fill_data(self,
268 user_id=self.anonymous_user.user_id)
267 user_id=self.anonymous_user.user_id)
269 #then we set this user is logged in
268 #then we set this user is logged in
270 self.is_authenticated = True
269 self.is_authenticated = True
271 else:
270 else:
272 self.is_authenticated = False
271 self.is_authenticated = False
273
272
274 log.debug('Auth User is now %s', self)
273 log.debug('Auth User is now %s', self)
275 user_model.fill_perms(self)
274 user_model.fill_perms(self)
276
275
277 @property
276 @property
278 def is_admin(self):
277 def is_admin(self):
279 return self.admin
278 return self.admin
280
279
281 @property
280 @property
282 def full_contact(self):
281 def full_contact(self):
283 return '%s %s <%s>' % (self.name, self.lastname, self.email)
282 return '%s %s <%s>' % (self.name, self.lastname, self.email)
284
283
285 def __repr__(self):
284 def __repr__(self):
286 return "<AuthUser('id:%s:%s|%s')>" % (self.user_id, self.username,
285 return "<AuthUser('id:%s:%s|%s')>" % (self.user_id, self.username,
287 self.is_authenticated)
286 self.is_authenticated)
288
287
289 def set_authenticated(self, authenticated=True):
288 def set_authenticated(self, authenticated=True):
290
289
291 if self.user_id != self.anonymous_user.user_id:
290 if self.user_id != self.anonymous_user.user_id:
292 self.is_authenticated = authenticated
291 self.is_authenticated = authenticated
293
292
294
293
295 def set_available_permissions(config):
294 def set_available_permissions(config):
296 """This function will propagate pylons globals with all available defined
295 """This function will propagate pylons globals with all available defined
297 permission given in db. We don't want to check each time from db for new
296 permission given in db. We don't want to check each time from db for new
298 permissions since adding a new permission also requires application restart
297 permissions since adding a new permission also requires application restart
299 ie. to decorate new views with the newly created permission
298 ie. to decorate new views with the newly created permission
300
299
301 :param config: current pylons config instance
300 :param config: current pylons config instance
302
301
303 """
302 """
304 log.info('getting information about all available permissions')
303 log.info('getting information about all available permissions')
305 try:
304 try:
306 sa = meta.Session()
305 sa = meta.Session()
307 all_perms = sa.query(Permission).all()
306 all_perms = sa.query(Permission).all()
308 except:
307 except:
309 pass
308 pass
310 finally:
309 finally:
311 meta.Session.remove()
310 meta.Session.remove()
312
311
313 config['available_permissions'] = [x.permission_name for x in all_perms]
312 config['available_permissions'] = [x.permission_name for x in all_perms]
314
313
315
314
316 #==============================================================================
315 #==============================================================================
317 # CHECK DECORATORS
316 # CHECK DECORATORS
318 #==============================================================================
317 #==============================================================================
319 class LoginRequired(object):
318 class LoginRequired(object):
320 """
319 """
321 Must be logged in to execute this function else
320 Must be logged in to execute this function else
322 redirect to login page
321 redirect to login page
323
322
324 :param api_access: if enabled this checks only for valid auth token
323 :param api_access: if enabled this checks only for valid auth token
325 and grants access based on valid token
324 and grants access based on valid token
326 """
325 """
327
326
328 def __init__(self, api_access=False):
327 def __init__(self, api_access=False):
329 self.api_access = api_access
328 self.api_access = api_access
330
329
331 def __call__(self, func):
330 def __call__(self, func):
332 return decorator(self.__wrapper, func)
331 return decorator(self.__wrapper, func)
333
332
334 def __wrapper(self, func, *fargs, **fkwargs):
333 def __wrapper(self, func, *fargs, **fkwargs):
335 cls = fargs[0]
334 cls = fargs[0]
336 user = cls.rhodecode_user
335 user = cls.rhodecode_user
337
336
338 api_access_ok = False
337 api_access_ok = False
339 if self.api_access:
338 if self.api_access:
340 log.debug('Checking API KEY access for %s', cls)
339 log.debug('Checking API KEY access for %s', cls)
341 if user.api_key == request.GET.get('api_key'):
340 if user.api_key == request.GET.get('api_key'):
342 api_access_ok = True
341 api_access_ok = True
343 else:
342 else:
344 log.debug("API KEY token not valid")
343 log.debug("API KEY token not valid")
345
344
346 log.debug('Checking if %s is authenticated @ %s', user.username, cls)
345 log.debug('Checking if %s is authenticated @ %s', user.username, cls)
347 if user.is_authenticated or api_access_ok:
346 if user.is_authenticated or api_access_ok:
348 log.debug('user %s is authenticated', user.username)
347 log.debug('user %s is authenticated', user.username)
349 return func(*fargs, **fkwargs)
348 return func(*fargs, **fkwargs)
350 else:
349 else:
351 log.warn('user %s NOT authenticated', user.username)
350 log.warn('user %s NOT authenticated', user.username)
352 p = url.current()
351 p = url.current()
353
352
354 log.debug('redirecting to login page with %s', p)
353 log.debug('redirecting to login page with %s', p)
355 return redirect(url('login_home', came_from=p))
354 return redirect(url('login_home', came_from=p))
356
355
357
356
358 class NotAnonymous(object):
357 class NotAnonymous(object):
359 """Must be logged in to execute this function else
358 """Must be logged in to execute this function else
360 redirect to login page"""
359 redirect to login page"""
361
360
362 def __call__(self, func):
361 def __call__(self, func):
363 return decorator(self.__wrapper, func)
362 return decorator(self.__wrapper, func)
364
363
365 def __wrapper(self, func, *fargs, **fkwargs):
364 def __wrapper(self, func, *fargs, **fkwargs):
366 cls = fargs[0]
365 cls = fargs[0]
367 self.user = cls.rhodecode_user
366 self.user = cls.rhodecode_user
368
367
369 log.debug('Checking if user is not anonymous @%s', cls)
368 log.debug('Checking if user is not anonymous @%s', cls)
370
369
371 anonymous = self.user.username == 'default'
370 anonymous = self.user.username == 'default'
372
371
373 if anonymous:
372 if anonymous:
374 p = url.current()
373 p = url.current()
375
374
376 import rhodecode.lib.helpers as h
375 import rhodecode.lib.helpers as h
377 h.flash(_('You need to be a registered user to '
376 h.flash(_('You need to be a registered user to '
378 'perform this action'),
377 'perform this action'),
379 category='warning')
378 category='warning')
380 return redirect(url('login_home', came_from=p))
379 return redirect(url('login_home', came_from=p))
381 else:
380 else:
382 return func(*fargs, **fkwargs)
381 return func(*fargs, **fkwargs)
383
382
384
383
385 class PermsDecorator(object):
384 class PermsDecorator(object):
386 """Base class for controller decorators"""
385 """Base class for controller decorators"""
387
386
388 def __init__(self, *required_perms):
387 def __init__(self, *required_perms):
389 available_perms = config['available_permissions']
388 available_perms = config['available_permissions']
390 for perm in required_perms:
389 for perm in required_perms:
391 if perm not in available_perms:
390 if perm not in available_perms:
392 raise Exception("'%s' permission is not defined" % perm)
391 raise Exception("'%s' permission is not defined" % perm)
393 self.required_perms = set(required_perms)
392 self.required_perms = set(required_perms)
394 self.user_perms = None
393 self.user_perms = None
395
394
396 def __call__(self, func):
395 def __call__(self, func):
397 return decorator(self.__wrapper, func)
396 return decorator(self.__wrapper, func)
398
397
399 def __wrapper(self, func, *fargs, **fkwargs):
398 def __wrapper(self, func, *fargs, **fkwargs):
400 cls = fargs[0]
399 cls = fargs[0]
401 self.user = cls.rhodecode_user
400 self.user = cls.rhodecode_user
402 self.user_perms = self.user.permissions
401 self.user_perms = self.user.permissions
403 log.debug('checking %s permissions %s for %s %s',
402 log.debug('checking %s permissions %s for %s %s',
404 self.__class__.__name__, self.required_perms, cls,
403 self.__class__.__name__, self.required_perms, cls,
405 self.user)
404 self.user)
406
405
407 if self.check_permissions():
406 if self.check_permissions():
408 log.debug('Permission granted for %s %s', cls, self.user)
407 log.debug('Permission granted for %s %s', cls, self.user)
409 return func(*fargs, **fkwargs)
408 return func(*fargs, **fkwargs)
410
409
411 else:
410 else:
412 log.warning('Permission denied for %s %s', cls, self.user)
411 log.warning('Permission denied for %s %s', cls, self.user)
413
412
414
413
415 anonymous = self.user.username == 'default'
414 anonymous = self.user.username == 'default'
416
415
417 if anonymous:
416 if anonymous:
418 p = url.current()
417 p = url.current()
419
418
420 import rhodecode.lib.helpers as h
419 import rhodecode.lib.helpers as h
421 h.flash(_('You need to be a signed in to '
420 h.flash(_('You need to be a signed in to '
422 'view this page'),
421 'view this page'),
423 category='warning')
422 category='warning')
424 return redirect(url('login_home', came_from=p))
423 return redirect(url('login_home', came_from=p))
425
424
426 else:
425 else:
427 #redirect with forbidden ret code
426 #redirect with forbidden ret code
428 return abort(403)
427 return abort(403)
429
428
430 def check_permissions(self):
429 def check_permissions(self):
431 """Dummy function for overriding"""
430 """Dummy function for overriding"""
432 raise Exception('You have to write this function in child class')
431 raise Exception('You have to write this function in child class')
433
432
434
433
435 class HasPermissionAllDecorator(PermsDecorator):
434 class HasPermissionAllDecorator(PermsDecorator):
436 """Checks for access permission for all given predicates. All of them
435 """Checks for access permission for all given predicates. All of them
437 have to be meet in order to fulfill the request
436 have to be meet in order to fulfill the request
438 """
437 """
439
438
440 def check_permissions(self):
439 def check_permissions(self):
441 if self.required_perms.issubset(self.user_perms.get('global')):
440 if self.required_perms.issubset(self.user_perms.get('global')):
442 return True
441 return True
443 return False
442 return False
444
443
445
444
446 class HasPermissionAnyDecorator(PermsDecorator):
445 class HasPermissionAnyDecorator(PermsDecorator):
447 """Checks for access permission for any of given predicates. In order to
446 """Checks for access permission for any of given predicates. In order to
448 fulfill the request any of predicates must be meet
447 fulfill the request any of predicates must be meet
449 """
448 """
450
449
451 def check_permissions(self):
450 def check_permissions(self):
452 if self.required_perms.intersection(self.user_perms.get('global')):
451 if self.required_perms.intersection(self.user_perms.get('global')):
453 return True
452 return True
454 return False
453 return False
455
454
456
455
457 class HasRepoPermissionAllDecorator(PermsDecorator):
456 class HasRepoPermissionAllDecorator(PermsDecorator):
458 """Checks for access permission for all given predicates for specific
457 """Checks for access permission for all given predicates for specific
459 repository. All of them have to be meet in order to fulfill the request
458 repository. All of them have to be meet in order to fulfill the request
460 """
459 """
461
460
462 def check_permissions(self):
461 def check_permissions(self):
463 repo_name = get_repo_slug(request)
462 repo_name = get_repo_slug(request)
464 try:
463 try:
465 user_perms = set([self.user_perms['repositories'][repo_name]])
464 user_perms = set([self.user_perms['repositories'][repo_name]])
466 except KeyError:
465 except KeyError:
467 return False
466 return False
468 if self.required_perms.issubset(user_perms):
467 if self.required_perms.issubset(user_perms):
469 return True
468 return True
470 return False
469 return False
471
470
472
471
473 class HasRepoPermissionAnyDecorator(PermsDecorator):
472 class HasRepoPermissionAnyDecorator(PermsDecorator):
474 """Checks for access permission for any of given predicates for specific
473 """Checks for access permission for any of given predicates for specific
475 repository. In order to fulfill the request any of predicates must be meet
474 repository. In order to fulfill the request any of predicates must be meet
476 """
475 """
477
476
478 def check_permissions(self):
477 def check_permissions(self):
479 repo_name = get_repo_slug(request)
478 repo_name = get_repo_slug(request)
480
479
481 try:
480 try:
482 user_perms = set([self.user_perms['repositories'][repo_name]])
481 user_perms = set([self.user_perms['repositories'][repo_name]])
483 except KeyError:
482 except KeyError:
484 return False
483 return False
485 if self.required_perms.intersection(user_perms):
484 if self.required_perms.intersection(user_perms):
486 return True
485 return True
487 return False
486 return False
488
487
489
488
490 #==============================================================================
489 #==============================================================================
491 # CHECK FUNCTIONS
490 # CHECK FUNCTIONS
492 #==============================================================================
491 #==============================================================================
493 class PermsFunction(object):
492 class PermsFunction(object):
494 """Base function for other check functions"""
493 """Base function for other check functions"""
495
494
496 def __init__(self, *perms):
495 def __init__(self, *perms):
497 available_perms = config['available_permissions']
496 available_perms = config['available_permissions']
498
497
499 for perm in perms:
498 for perm in perms:
500 if perm not in available_perms:
499 if perm not in available_perms:
501 raise Exception("'%s' permission in not defined" % perm)
500 raise Exception("'%s' permission in not defined" % perm)
502 self.required_perms = set(perms)
501 self.required_perms = set(perms)
503 self.user_perms = None
502 self.user_perms = None
504 self.granted_for = ''
503 self.granted_for = ''
505 self.repo_name = None
504 self.repo_name = None
506
505
507 def __call__(self, check_Location=''):
506 def __call__(self, check_Location=''):
508 user = session.get('rhodecode_user', False)
507 user = session.get('rhodecode_user', False)
509 if not user:
508 if not user:
510 return False
509 return False
511 self.user_perms = user.permissions
510 self.user_perms = user.permissions
512 self.granted_for = user
511 self.granted_for = user
513 log.debug('checking %s %s %s', self.__class__.__name__,
512 log.debug('checking %s %s %s', self.__class__.__name__,
514 self.required_perms, user)
513 self.required_perms, user)
515
514
516 if self.check_permissions():
515 if self.check_permissions():
517 log.debug('Permission granted %s @ %s', self.granted_for,
516 log.debug('Permission granted %s @ %s', self.granted_for,
518 check_Location or 'unspecified location')
517 check_Location or 'unspecified location')
519 return True
518 return True
520
519
521 else:
520 else:
522 log.warning('Permission denied for %s @ %s', self.granted_for,
521 log.warning('Permission denied for %s @ %s', self.granted_for,
523 check_Location or 'unspecified location')
522 check_Location or 'unspecified location')
524 return False
523 return False
525
524
526 def check_permissions(self):
525 def check_permissions(self):
527 """Dummy function for overriding"""
526 """Dummy function for overriding"""
528 raise Exception('You have to write this function in child class')
527 raise Exception('You have to write this function in child class')
529
528
530
529
531 class HasPermissionAll(PermsFunction):
530 class HasPermissionAll(PermsFunction):
532 def check_permissions(self):
531 def check_permissions(self):
533 if self.required_perms.issubset(self.user_perms.get('global')):
532 if self.required_perms.issubset(self.user_perms.get('global')):
534 return True
533 return True
535 return False
534 return False
536
535
537
536
538 class HasPermissionAny(PermsFunction):
537 class HasPermissionAny(PermsFunction):
539 def check_permissions(self):
538 def check_permissions(self):
540 if self.required_perms.intersection(self.user_perms.get('global')):
539 if self.required_perms.intersection(self.user_perms.get('global')):
541 return True
540 return True
542 return False
541 return False
543
542
544
543
545 class HasRepoPermissionAll(PermsFunction):
544 class HasRepoPermissionAll(PermsFunction):
546
545
547 def __call__(self, repo_name=None, check_Location=''):
546 def __call__(self, repo_name=None, check_Location=''):
548 self.repo_name = repo_name
547 self.repo_name = repo_name
549 return super(HasRepoPermissionAll, self).__call__(check_Location)
548 return super(HasRepoPermissionAll, self).__call__(check_Location)
550
549
551 def check_permissions(self):
550 def check_permissions(self):
552 if not self.repo_name:
551 if not self.repo_name:
553 self.repo_name = get_repo_slug(request)
552 self.repo_name = get_repo_slug(request)
554
553
555 try:
554 try:
556 self.user_perms = set([self.user_perms['reposit'
555 self.user_perms = set([self.user_perms['reposit'
557 'ories'][self.repo_name]])
556 'ories'][self.repo_name]])
558 except KeyError:
557 except KeyError:
559 return False
558 return False
560 self.granted_for = self.repo_name
559 self.granted_for = self.repo_name
561 if self.required_perms.issubset(self.user_perms):
560 if self.required_perms.issubset(self.user_perms):
562 return True
561 return True
563 return False
562 return False
564
563
565
564
566 class HasRepoPermissionAny(PermsFunction):
565 class HasRepoPermissionAny(PermsFunction):
567
566
568 def __call__(self, repo_name=None, check_Location=''):
567 def __call__(self, repo_name=None, check_Location=''):
569 self.repo_name = repo_name
568 self.repo_name = repo_name
570 return super(HasRepoPermissionAny, self).__call__(check_Location)
569 return super(HasRepoPermissionAny, self).__call__(check_Location)
571
570
572 def check_permissions(self):
571 def check_permissions(self):
573 if not self.repo_name:
572 if not self.repo_name:
574 self.repo_name = get_repo_slug(request)
573 self.repo_name = get_repo_slug(request)
575
574
576 try:
575 try:
577 self.user_perms = set([self.user_perms['reposi'
576 self.user_perms = set([self.user_perms['reposi'
578 'tories'][self.repo_name]])
577 'tories'][self.repo_name]])
579 except KeyError:
578 except KeyError:
580 return False
579 return False
581 self.granted_for = self.repo_name
580 self.granted_for = self.repo_name
582 if self.required_perms.intersection(self.user_perms):
581 if self.required_perms.intersection(self.user_perms):
583 return True
582 return True
584 return False
583 return False
585
584
586
585
587 #==============================================================================
586 #==============================================================================
588 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
587 # SPECIAL VERSION TO HANDLE MIDDLEWARE AUTH
589 #==============================================================================
588 #==============================================================================
590 class HasPermissionAnyMiddleware(object):
589 class HasPermissionAnyMiddleware(object):
591 def __init__(self, *perms):
590 def __init__(self, *perms):
592 self.required_perms = set(perms)
591 self.required_perms = set(perms)
593
592
594 def __call__(self, user, repo_name):
593 def __call__(self, user, repo_name):
595 usr = AuthUser(user.user_id)
594 usr = AuthUser(user.user_id)
596 try:
595 try:
597 self.user_perms = set([usr.permissions['repositories'][repo_name]])
596 self.user_perms = set([usr.permissions['repositories'][repo_name]])
598 except:
597 except:
599 self.user_perms = set()
598 self.user_perms = set()
600 self.granted_for = ''
599 self.granted_for = ''
601 self.username = user.username
600 self.username = user.username
602 self.repo_name = repo_name
601 self.repo_name = repo_name
603 return self.check_permissions()
602 return self.check_permissions()
604
603
605 def check_permissions(self):
604 def check_permissions(self):
606 log.debug('checking mercurial protocol '
605 log.debug('checking mercurial protocol '
607 'permissions %s for user:%s repository:%s', self.user_perms,
606 'permissions %s for user:%s repository:%s', self.user_perms,
608 self.username, self.repo_name)
607 self.username, self.repo_name)
609 if self.required_perms.intersection(self.user_perms):
608 if self.required_perms.intersection(self.user_perms):
610 log.debug('permission granted')
609 log.debug('permission granted')
611 return True
610 return True
612 log.debug('permission denied')
611 log.debug('permission denied')
613 return False
612 return False
@@ -1,101 +1,101
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.backup_manager
3 rhodecode.lib.backup_manager
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Mercurial repositories backup manager, it allows to backups all
6 Mercurial repositories backup manager, it allows to backups all
7 repositories and send it to backup server using RSA key via ssh.
7 repositories and send it to backup server using RSA key via ssh.
8
8
9 :created_on: Feb 28, 2010
9 :created_on: Feb 28, 2010
10 :copyright: (c) 2010 by marcink.
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :license: LICENSE_NAME, see LICENSE_FILE for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import os
26 import os
27 import sys
27 import sys
28
28
29 import logging
29 import logging
30 import tarfile
30 import tarfile
31 import datetime
31 import datetime
32 import subprocess
32 import subprocess
33
33
34 logging.basicConfig(level=logging.DEBUG,
34 logging.basicConfig(level=logging.DEBUG,
35 format="%(asctime)s %(levelname)-5.5s %(message)s")
35 format="%(asctime)s %(levelname)-5.5s %(message)s")
36
36
37
37
38 class BackupManager(object):
38 class BackupManager(object):
39 def __init__(self, repos_location, rsa_key, backup_server):
39 def __init__(self, repos_location, rsa_key, backup_server):
40 today = datetime.datetime.now().weekday() + 1
40 today = datetime.datetime.now().weekday() + 1
41 self.backup_file_name = "mercurial_repos.%s.tar.gz" % today
41 self.backup_file_name = "mercurial_repos.%s.tar.gz" % today
42
42
43 self.id_rsa_path = self.get_id_rsa(rsa_key)
43 self.id_rsa_path = self.get_id_rsa(rsa_key)
44 self.repos_path = self.get_repos_path(repos_location)
44 self.repos_path = self.get_repos_path(repos_location)
45 self.backup_server = backup_server
45 self.backup_server = backup_server
46
46
47 self.backup_file_path = '/tmp'
47 self.backup_file_path = '/tmp'
48
48
49 logging.info('starting backup for %s', self.repos_path)
49 logging.info('starting backup for %s', self.repos_path)
50 logging.info('backup target %s', self.backup_file_path)
50 logging.info('backup target %s', self.backup_file_path)
51
51
52 def get_id_rsa(self, rsa_key):
52 def get_id_rsa(self, rsa_key):
53 if not os.path.isfile(rsa_key):
53 if not os.path.isfile(rsa_key):
54 logging.error('Could not load id_rsa key file in %s', rsa_key)
54 logging.error('Could not load id_rsa key file in %s', rsa_key)
55 sys.exit()
55 sys.exit()
56 return rsa_key
56 return rsa_key
57
57
58 def get_repos_path(self, path):
58 def get_repos_path(self, path):
59 if not os.path.isdir(path):
59 if not os.path.isdir(path):
60 logging.error('Wrong location for repositories in %s', path)
60 logging.error('Wrong location for repositories in %s', path)
61 sys.exit()
61 sys.exit()
62 return path
62 return path
63
63
64 def backup_repos(self):
64 def backup_repos(self):
65 bckp_file = os.path.join(self.backup_file_path, self.backup_file_name)
65 bckp_file = os.path.join(self.backup_file_path, self.backup_file_name)
66 tar = tarfile.open(bckp_file, "w:gz")
66 tar = tarfile.open(bckp_file, "w:gz")
67
67
68 for dir_name in os.listdir(self.repos_path):
68 for dir_name in os.listdir(self.repos_path):
69 logging.info('backing up %s', dir_name)
69 logging.info('backing up %s', dir_name)
70 tar.add(os.path.join(self.repos_path, dir_name), dir_name)
70 tar.add(os.path.join(self.repos_path, dir_name), dir_name)
71 tar.close()
71 tar.close()
72 logging.info('finished backup of mercurial repositories')
72 logging.info('finished backup of mercurial repositories')
73
73
74 def transfer_files(self):
74 def transfer_files(self):
75 params = {
75 params = {
76 'id_rsa_key': self.id_rsa_path,
76 'id_rsa_key': self.id_rsa_path,
77 'backup_file': os.path.join(self.backup_file_path,
77 'backup_file': os.path.join(self.backup_file_path,
78 self.backup_file_name),
78 self.backup_file_name),
79 'backup_server': self.backup_server
79 'backup_server': self.backup_server
80 }
80 }
81 cmd = ['scp', '-l', '40000', '-i', '%(id_rsa_key)s' % params,
81 cmd = ['scp', '-l', '40000', '-i', '%(id_rsa_key)s' % params,
82 '%(backup_file)s' % params,
82 '%(backup_file)s' % params,
83 '%(backup_server)s' % params]
83 '%(backup_server)s' % params]
84
84
85 subprocess.call(cmd)
85 subprocess.call(cmd)
86 logging.info('Transfered file %s to %s', self.backup_file_name, cmd[4])
86 logging.info('Transfered file %s to %s', self.backup_file_name, cmd[4])
87
87
88 def rm_file(self):
88 def rm_file(self):
89 logging.info('Removing file %s', self.backup_file_name)
89 logging.info('Removing file %s', self.backup_file_name)
90 os.remove(os.path.join(self.backup_file_path, self.backup_file_name))
90 os.remove(os.path.join(self.backup_file_path, self.backup_file_name))
91
91
92 if __name__ == "__main__":
92 if __name__ == "__main__":
93
93
94 repo_location = '/home/repo_path'
94 repo_location = '/home/repo_path'
95 backup_server = 'root@192.168.1.100:/backups/mercurial'
95 backup_server = 'root@192.168.1.100:/backups/mercurial'
96 rsa_key = '/home/id_rsa'
96 rsa_key = '/home/id_rsa'
97
97
98 B_MANAGER = BackupManager(repo_location, rsa_key, backup_server)
98 B_MANAGER = BackupManager(repo_location, rsa_key, backup_server)
99 B_MANAGER.backup_repos()
99 B_MANAGER.backup_repos()
100 B_MANAGER.transfer_files()
100 B_MANAGER.transfer_files()
101 B_MANAGER.rm_file()
101 B_MANAGER.rm_file()
@@ -1,82 +1,82
1 """The base Controller API
1 """The base Controller API
2
2
3 Provides the BaseController class for subclassing.
3 Provides the BaseController class for subclassing.
4 """
4 """
5 import logging
5 import logging
6
6
7 from pylons import config, tmpl_context as c, request, session, url
7 from pylons import config, tmpl_context as c, request, session, url
8 from pylons.controllers import WSGIController
8 from pylons.controllers import WSGIController
9 from pylons.controllers.util import redirect
9 from pylons.controllers.util import redirect
10 from pylons.templating import render_mako as render
10 from pylons.templating import render_mako as render
11
11
12 from rhodecode import __version__
12 from rhodecode import __version__
13 from rhodecode.lib.auth import AuthUser
13 from rhodecode.lib.auth import AuthUser
14 from rhodecode.lib.utils import get_repo_slug
14 from rhodecode.lib.utils import get_repo_slug
15 from rhodecode.model import meta
15 from rhodecode.model import meta
16 from rhodecode.model.scm import ScmModel
16 from rhodecode.model.scm import ScmModel
17 from rhodecode import BACKENDS
17 from rhodecode import BACKENDS
18 from rhodecode.model.db import Repository
18 from rhodecode.model.db import Repository
19
19
20 log = logging.getLogger(__name__)
20 log = logging.getLogger(__name__)
21
21
22 class BaseController(WSGIController):
22 class BaseController(WSGIController):
23
23
24 def __before__(self):
24 def __before__(self):
25 c.rhodecode_version = __version__
25 c.rhodecode_version = __version__
26 c.rhodecode_name = config.get('rhodecode_title')
26 c.rhodecode_name = config.get('rhodecode_title')
27 c.ga_code = config.get('rhodecode_ga_code')
27 c.ga_code = config.get('rhodecode_ga_code')
28 c.repo_name = get_repo_slug(request)
28 c.repo_name = get_repo_slug(request)
29 c.backends = BACKENDS.keys()
29 c.backends = BACKENDS.keys()
30 self.cut_off_limit = int(config.get('cut_off_limit'))
30 self.cut_off_limit = int(config.get('cut_off_limit'))
31
31
32 self.sa = meta.Session()
32 self.sa = meta.Session()
33 self.scm_model = ScmModel(self.sa)
33 self.scm_model = ScmModel(self.sa)
34
34
35 #c.unread_journal = scm_model.get_unread_journal()
35 #c.unread_journal = scm_model.get_unread_journal()
36
36
37 def __call__(self, environ, start_response):
37 def __call__(self, environ, start_response):
38 """Invoke the Controller"""
38 """Invoke the Controller"""
39 # WSGIController.__call__ dispatches to the Controller method
39 # WSGIController.__call__ dispatches to the Controller method
40 # the request is routed to. This routing information is
40 # the request is routed to. This routing information is
41 # available in environ['pylons.routes_dict']
41 # available in environ['pylons.routes_dict']
42 try:
42 try:
43 # putting this here makes sure that we update permissions each time
43 # putting this here makes sure that we update permissions each time
44 api_key = request.GET.get('api_key')
44 api_key = request.GET.get('api_key')
45 user_id = getattr(session.get('rhodecode_user'), 'user_id', None)
45 user_id = getattr(session.get('rhodecode_user'), 'user_id', None)
46 self.rhodecode_user = c.rhodecode_user = AuthUser(user_id, api_key)
46 self.rhodecode_user = c.rhodecode_user = AuthUser(user_id, api_key)
47 self.rhodecode_user.set_authenticated(
47 self.rhodecode_user.set_authenticated(
48 getattr(session.get('rhodecode_user'),
48 getattr(session.get('rhodecode_user'),
49 'is_authenticated', False))
49 'is_authenticated', False))
50 session['rhodecode_user'] = self.rhodecode_user
50 session['rhodecode_user'] = self.rhodecode_user
51 session.save()
51 session.save()
52 return WSGIController.__call__(self, environ, start_response)
52 return WSGIController.__call__(self, environ, start_response)
53 finally:
53 finally:
54 meta.Session.remove()
54 meta.Session.remove()
55
55
56
56
57 class BaseRepoController(BaseController):
57 class BaseRepoController(BaseController):
58 """
58 """
59 Base class for controllers responsible for loading all needed data
59 Base class for controllers responsible for loading all needed data
60 for those controllers, loaded items are
60 for those controllers, loaded items are
61
61
62 c.rhodecode_repo: instance of scm repository (taken from cache)
62 c.rhodecode_repo: instance of scm repository (taken from cache)
63
63
64 """
64 """
65
65
66 def __before__(self):
66 def __before__(self):
67 super(BaseRepoController, self).__before__()
67 super(BaseRepoController, self).__before__()
68 if c.repo_name:
68 if c.repo_name:
69
69
70 c.rhodecode_db_repo = Repository.by_repo_name(c.repo_name)
70 c.rhodecode_db_repo = Repository.get_by_repo_name(c.repo_name)
71 c.rhodecode_repo = c.rhodecode_db_repo.scm_instance
71 c.rhodecode_repo = c.rhodecode_db_repo.scm_instance
72
72
73 if c.rhodecode_repo is None:
73 if c.rhodecode_repo is None:
74 log.error('%s this repository is present in database but it '
74 log.error('%s this repository is present in database but it '
75 'cannot be created as an scm instance', c.repo_name)
75 'cannot be created as an scm instance', c.repo_name)
76
76
77 redirect(url('home'))
77 redirect(url('home'))
78
78
79 c.repository_followers = \
79 c.repository_followers = \
80 self.scm_model.get_followers(c.repo_name)
80 self.scm_model.get_followers(c.repo_name)
81 c.repository_forks = self.scm_model.get_forks(c.repo_name)
81 c.repository_forks = self.scm_model.get_forks(c.repo_name)
82
82
@@ -1,109 +1,109
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.celerylib.__init__
3 rhodecode.lib.celerylib.__init__
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 celery libs for RhodeCode
6 celery libs for RhodeCode
7
7
8 :created_on: Nov 27, 2010
8 :created_on: Nov 27, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import os
26 import os
27 import sys
27 import sys
28 import socket
28 import socket
29 import traceback
29 import traceback
30 import logging
30 import logging
31 from os.path import dirname as dn, join as jn
31 from os.path import dirname as dn, join as jn
32
32
33 from hashlib import md5
33 from hashlib import md5
34 from decorator import decorator
34 from decorator import decorator
35 from pylons import config
35 from pylons import config
36
36
37 from vcs.utils.lazy import LazyProperty
37 from vcs.utils.lazy import LazyProperty
38
38
39 from rhodecode.lib import str2bool
39 from rhodecode.lib import str2bool
40 from rhodecode.lib.pidlock import DaemonLock, LockHeld
40 from rhodecode.lib.pidlock import DaemonLock, LockHeld
41
41
42 from celery.messaging import establish_connection
42 from celery.messaging import establish_connection
43
43
44
44
45 log = logging.getLogger(__name__)
45 log = logging.getLogger(__name__)
46
46
47 try:
47 try:
48 CELERY_ON = str2bool(config['app_conf'].get('use_celery'))
48 CELERY_ON = str2bool(config['app_conf'].get('use_celery'))
49 except KeyError:
49 except KeyError:
50 CELERY_ON = False
50 CELERY_ON = False
51
51
52
52
53 class ResultWrapper(object):
53 class ResultWrapper(object):
54 def __init__(self, task):
54 def __init__(self, task):
55 self.task = task
55 self.task = task
56
56
57 @LazyProperty
57 @LazyProperty
58 def result(self):
58 def result(self):
59 return self.task
59 return self.task
60
60
61
61
62 def run_task(task, *args, **kwargs):
62 def run_task(task, *args, **kwargs):
63 if CELERY_ON:
63 if CELERY_ON:
64 try:
64 try:
65 t = task.apply_async(args=args, kwargs=kwargs)
65 t = task.apply_async(args=args, kwargs=kwargs)
66 log.info('running task %s:%s', t.task_id, task)
66 log.info('running task %s:%s', t.task_id, task)
67 return t
67 return t
68
68
69 except socket.error, e:
69 except socket.error, e:
70 if isinstance(e, IOError) and e.errno == 111:
70 if isinstance(e, IOError) and e.errno == 111:
71 log.debug('Unable to connect to celeryd. Sync execution')
71 log.debug('Unable to connect to celeryd. Sync execution')
72 else:
72 else:
73 log.error(traceback.format_exc())
73 log.error(traceback.format_exc())
74 except KeyError, e:
74 except KeyError, e:
75 log.debug('Unable to connect to celeryd. Sync execution')
75 log.debug('Unable to connect to celeryd. Sync execution')
76 except Exception, e:
76 except Exception, e:
77 log.error(traceback.format_exc())
77 log.error(traceback.format_exc())
78
78
79 log.debug('executing task %s in sync mode', task)
79 log.debug('executing task %s in sync mode', task)
80 return ResultWrapper(task(*args, **kwargs))
80 return ResultWrapper(task(*args, **kwargs))
81
81
82
82
83 def __get_lockkey(func, *fargs, **fkwargs):
83 def __get_lockkey(func, *fargs, **fkwargs):
84 params = list(fargs)
84 params = list(fargs)
85 params.extend(['%s-%s' % ar for ar in fkwargs.items()])
85 params.extend(['%s-%s' % ar for ar in fkwargs.items()])
86
86
87 func_name = str(func.__name__) if hasattr(func, '__name__') else str(func)
87 func_name = str(func.__name__) if hasattr(func, '__name__') else str(func)
88
88
89 lockkey = 'task_%s.lock' % \
89 lockkey = 'task_%s.lock' % \
90 md5(func_name + '-' + '-'.join(map(str, params))).hexdigest()
90 md5(func_name + '-' + '-'.join(map(str, params))).hexdigest()
91 return lockkey
91 return lockkey
92
92
93
93
94 def locked_task(func):
94 def locked_task(func):
95 def __wrapper(func, *fargs, **fkwargs):
95 def __wrapper(func, *fargs, **fkwargs):
96 lockkey = __get_lockkey(func, *fargs, **fkwargs)
96 lockkey = __get_lockkey(func, *fargs, **fkwargs)
97 lockkey_path = dn(dn(dn(os.path.abspath(__file__))))
97 lockkey_path = config['here']
98
98
99 log.info('running task with lockkey %s', lockkey)
99 log.info('running task with lockkey %s', lockkey)
100 try:
100 try:
101 l = DaemonLock(jn(lockkey_path, lockkey))
101 l = DaemonLock(file_=jn(lockkey_path, lockkey))
102 ret = func(*fargs, **fkwargs)
102 ret = func(*fargs, **fkwargs)
103 l.release()
103 l.release()
104 return ret
104 return ret
105 except LockHeld:
105 except LockHeld:
106 log.info('LockHeld')
106 log.info('LockHeld')
107 return 'Task with key %s already running' % lockkey
107 return 'Task with key %s already running' % lockkey
108
108
109 return decorator(__wrapper, func)
109 return decorator(__wrapper, func)
@@ -1,410 +1,411
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.celerylib.tasks
3 rhodecode.lib.celerylib.tasks
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 RhodeCode task modules, containing all task that suppose to be run
6 RhodeCode task modules, containing all task that suppose to be run
7 by celery daemon
7 by celery daemon
8
8
9 :created_on: Oct 6, 2010
9 :created_on: Oct 6, 2010
10 :author: marcink
10 :author: marcink
11 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
12 :license: GPLv3, see COPYING for more details.
12 :license: GPLv3, see COPYING for more details.
13 """
13 """
14 # This program is free software: you can redistribute it and/or modify
14 # This program is free software: you can redistribute it and/or modify
15 # it under the terms of the GNU General Public License as published by
15 # it under the terms of the GNU General Public License as published by
16 # the Free Software Foundation, either version 3 of the License, or
16 # the Free Software Foundation, either version 3 of the License, or
17 # (at your option) any later version.
17 # (at your option) any later version.
18 #
18 #
19 # This program is distributed in the hope that it will be useful,
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # GNU General Public License for more details.
22 # GNU General Public License for more details.
23 #
23 #
24 # You should have received a copy of the GNU General Public License
24 # You should have received a copy of the GNU General Public License
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 from celery.decorators import task
26 from celery.decorators import task
27
27
28 import os
28 import os
29 import traceback
29 import traceback
30 import logging
30 import logging
31 from os.path import dirname as dn, join as jn
31 from os.path import dirname as dn, join as jn
32
32
33 from time import mktime
33 from time import mktime
34 from operator import itemgetter
34 from operator import itemgetter
35 from string import lower
35 from string import lower
36
36
37 from pylons import config, url
37 from pylons import config, url
38 from pylons.i18n.translation import _
38 from pylons.i18n.translation import _
39
39
40 from rhodecode.lib import LANGUAGES_EXTENSIONS_MAP, safe_str
40 from rhodecode.lib import LANGUAGES_EXTENSIONS_MAP, safe_str
41 from rhodecode.lib.celerylib import run_task, locked_task, str2bool, \
41 from rhodecode.lib.celerylib import run_task, locked_task, str2bool, \
42 __get_lockkey, LockHeld, DaemonLock
42 __get_lockkey, LockHeld, DaemonLock
43 from rhodecode.lib.helpers import person
43 from rhodecode.lib.helpers import person
44 from rhodecode.lib.smtp_mailer import SmtpMailer
44 from rhodecode.lib.smtp_mailer import SmtpMailer
45 from rhodecode.lib.utils import add_cache
45 from rhodecode.lib.utils import add_cache
46 from rhodecode.lib.compat import json, OrderedDict
46 from rhodecode.lib.compat import json, OrderedDict
47
47
48 from rhodecode.model import init_model
48 from rhodecode.model import init_model
49 from rhodecode.model import meta
49 from rhodecode.model import meta
50 from rhodecode.model.db import RhodeCodeUi, Statistics, Repository
50 from rhodecode.model.db import RhodeCodeUi, Statistics, Repository
51
51
52 from vcs.backends import get_repo
52 from vcs.backends import get_repo
53
53
54 from sqlalchemy import engine_from_config
54 from sqlalchemy import engine_from_config
55
55
56 add_cache(config)
56 add_cache(config)
57
57
58
58
59
59
60 __all__ = ['whoosh_index', 'get_commits_stats',
60 __all__ = ['whoosh_index', 'get_commits_stats',
61 'reset_user_password', 'send_email']
61 'reset_user_password', 'send_email']
62
62
63 CELERY_ON = str2bool(config['app_conf'].get('use_celery'))
63 CELERY_ON = str2bool(config['app_conf'].get('use_celery'))
64
64
65
65
66 def get_session():
66 def get_session():
67 if CELERY_ON:
67 if CELERY_ON:
68 engine = engine_from_config(config, 'sqlalchemy.db1.')
68 engine = engine_from_config(config, 'sqlalchemy.db1.')
69 init_model(engine)
69 init_model(engine)
70 sa = meta.Session()
70 sa = meta.Session()
71 return sa
71 return sa
72
72
73
73
74 def get_repos_path():
74 def get_repos_path():
75 sa = get_session()
75 sa = get_session()
76 q = sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
76 q = sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
77 return q.ui_value
77 return q.ui_value
78
78
79
79
80 @task(ignore_result=True)
80 @task(ignore_result=True)
81 @locked_task
81 @locked_task
82 def whoosh_index(repo_location, full_index):
82 def whoosh_index(repo_location, full_index):
83 #log = whoosh_index.get_logger()
83 #log = whoosh_index.get_logger()
84 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
84 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
85 index_location = config['index_dir']
85 index_location = config['index_dir']
86 WhooshIndexingDaemon(index_location=index_location,
86 WhooshIndexingDaemon(index_location=index_location,
87 repo_location=repo_location, sa=get_session())\
87 repo_location=repo_location, sa=get_session())\
88 .run(full_index=full_index)
88 .run(full_index=full_index)
89
89
90
90
91 @task(ignore_result=True)
91 @task(ignore_result=True)
92 def get_commits_stats(repo_name, ts_min_y, ts_max_y):
92 def get_commits_stats(repo_name, ts_min_y, ts_max_y):
93 try:
93 try:
94 log = get_commits_stats.get_logger()
94 log = get_commits_stats.get_logger()
95 except:
95 except:
96 log = logging.getLogger(__name__)
96 log = logging.getLogger(__name__)
97
97
98 lockkey = __get_lockkey('get_commits_stats', repo_name, ts_min_y,
98 lockkey = __get_lockkey('get_commits_stats', repo_name, ts_min_y,
99 ts_max_y)
99 ts_max_y)
100 lockkey_path = dn(dn(dn(dn(os.path.abspath(__file__)))))
100 lockkey_path = config['here']
101
101 log.info('running task with lockkey %s', lockkey)
102 log.info('running task with lockkey %s', lockkey)
102 try:
103 try:
103 lock = l = DaemonLock(jn(lockkey_path, lockkey))
104 lock = l = DaemonLock(file_=jn(lockkey_path, lockkey))
104
105
105 #for js data compatibilty cleans the key for person from '
106 #for js data compatibilty cleans the key for person from '
106 akc = lambda k: person(k).replace('"', "")
107 akc = lambda k: person(k).replace('"', "")
107
108
108 co_day_auth_aggr = {}
109 co_day_auth_aggr = {}
109 commits_by_day_aggregate = {}
110 commits_by_day_aggregate = {}
110 repos_path = get_repos_path()
111 repos_path = get_repos_path()
111 repo = get_repo(safe_str(os.path.join(repos_path, repo_name)))
112 repo = get_repo(safe_str(os.path.join(repos_path, repo_name)))
112 repo_size = len(repo.revisions)
113 repo_size = len(repo.revisions)
113 #return if repo have no revisions
114 #return if repo have no revisions
114 if repo_size < 1:
115 if repo_size < 1:
115 lock.release()
116 lock.release()
116 return True
117 return True
117
118
118 skip_date_limit = True
119 skip_date_limit = True
119 parse_limit = int(config['app_conf'].get('commit_parse_limit'))
120 parse_limit = int(config['app_conf'].get('commit_parse_limit'))
120 last_rev = 0
121 last_rev = 0
121 last_cs = None
122 last_cs = None
122 timegetter = itemgetter('time')
123 timegetter = itemgetter('time')
123
124
124 sa = get_session()
125 sa = get_session()
125
126
126 dbrepo = sa.query(Repository)\
127 dbrepo = sa.query(Repository)\
127 .filter(Repository.repo_name == repo_name).scalar()
128 .filter(Repository.repo_name == repo_name).scalar()
128 cur_stats = sa.query(Statistics)\
129 cur_stats = sa.query(Statistics)\
129 .filter(Statistics.repository == dbrepo).scalar()
130 .filter(Statistics.repository == dbrepo).scalar()
130
131
131 if cur_stats is not None:
132 if cur_stats is not None:
132 last_rev = cur_stats.stat_on_revision
133 last_rev = cur_stats.stat_on_revision
133
134
134 if last_rev == repo.get_changeset().revision and repo_size > 1:
135 if last_rev == repo.get_changeset().revision and repo_size > 1:
135 #pass silently without any work if we're not on first revision or
136 #pass silently without any work if we're not on first revision or
136 #current state of parsing revision(from db marker) is the
137 #current state of parsing revision(from db marker) is the
137 #last revision
138 #last revision
138 lock.release()
139 lock.release()
139 return True
140 return True
140
141
141 if cur_stats:
142 if cur_stats:
142 commits_by_day_aggregate = OrderedDict(json.loads(
143 commits_by_day_aggregate = OrderedDict(json.loads(
143 cur_stats.commit_activity_combined))
144 cur_stats.commit_activity_combined))
144 co_day_auth_aggr = json.loads(cur_stats.commit_activity)
145 co_day_auth_aggr = json.loads(cur_stats.commit_activity)
145
146
146 log.debug('starting parsing %s', parse_limit)
147 log.debug('starting parsing %s', parse_limit)
147 lmktime = mktime
148 lmktime = mktime
148
149
149 last_rev = last_rev + 1 if last_rev > 0 else last_rev
150 last_rev = last_rev + 1 if last_rev > 0 else last_rev
150
151
151 for cs in repo[last_rev:last_rev + parse_limit]:
152 for cs in repo[last_rev:last_rev + parse_limit]:
152 last_cs = cs # remember last parsed changeset
153 last_cs = cs # remember last parsed changeset
153 k = lmktime([cs.date.timetuple()[0], cs.date.timetuple()[1],
154 k = lmktime([cs.date.timetuple()[0], cs.date.timetuple()[1],
154 cs.date.timetuple()[2], 0, 0, 0, 0, 0, 0])
155 cs.date.timetuple()[2], 0, 0, 0, 0, 0, 0])
155
156
156 if akc(cs.author) in co_day_auth_aggr:
157 if akc(cs.author) in co_day_auth_aggr:
157 try:
158 try:
158 l = [timegetter(x) for x in
159 l = [timegetter(x) for x in
159 co_day_auth_aggr[akc(cs.author)]['data']]
160 co_day_auth_aggr[akc(cs.author)]['data']]
160 time_pos = l.index(k)
161 time_pos = l.index(k)
161 except ValueError:
162 except ValueError:
162 time_pos = False
163 time_pos = False
163
164
164 if time_pos >= 0 and time_pos is not False:
165 if time_pos >= 0 and time_pos is not False:
165
166
166 datadict = \
167 datadict = \
167 co_day_auth_aggr[akc(cs.author)]['data'][time_pos]
168 co_day_auth_aggr[akc(cs.author)]['data'][time_pos]
168
169
169 datadict["commits"] += 1
170 datadict["commits"] += 1
170 datadict["added"] += len(cs.added)
171 datadict["added"] += len(cs.added)
171 datadict["changed"] += len(cs.changed)
172 datadict["changed"] += len(cs.changed)
172 datadict["removed"] += len(cs.removed)
173 datadict["removed"] += len(cs.removed)
173
174
174 else:
175 else:
175 if k >= ts_min_y and k <= ts_max_y or skip_date_limit:
176 if k >= ts_min_y and k <= ts_max_y or skip_date_limit:
176
177
177 datadict = {"time": k,
178 datadict = {"time": k,
178 "commits": 1,
179 "commits": 1,
179 "added": len(cs.added),
180 "added": len(cs.added),
180 "changed": len(cs.changed),
181 "changed": len(cs.changed),
181 "removed": len(cs.removed),
182 "removed": len(cs.removed),
182 }
183 }
183 co_day_auth_aggr[akc(cs.author)]['data']\
184 co_day_auth_aggr[akc(cs.author)]['data']\
184 .append(datadict)
185 .append(datadict)
185
186
186 else:
187 else:
187 if k >= ts_min_y and k <= ts_max_y or skip_date_limit:
188 if k >= ts_min_y and k <= ts_max_y or skip_date_limit:
188 co_day_auth_aggr[akc(cs.author)] = {
189 co_day_auth_aggr[akc(cs.author)] = {
189 "label": akc(cs.author),
190 "label": akc(cs.author),
190 "data": [{"time":k,
191 "data": [{"time":k,
191 "commits":1,
192 "commits":1,
192 "added":len(cs.added),
193 "added":len(cs.added),
193 "changed":len(cs.changed),
194 "changed":len(cs.changed),
194 "removed":len(cs.removed),
195 "removed":len(cs.removed),
195 }],
196 }],
196 "schema": ["commits"],
197 "schema": ["commits"],
197 }
198 }
198
199
199 #gather all data by day
200 #gather all data by day
200 if k in commits_by_day_aggregate:
201 if k in commits_by_day_aggregate:
201 commits_by_day_aggregate[k] += 1
202 commits_by_day_aggregate[k] += 1
202 else:
203 else:
203 commits_by_day_aggregate[k] = 1
204 commits_by_day_aggregate[k] = 1
204
205
205 overview_data = sorted(commits_by_day_aggregate.items(),
206 overview_data = sorted(commits_by_day_aggregate.items(),
206 key=itemgetter(0))
207 key=itemgetter(0))
207
208
208 if not co_day_auth_aggr:
209 if not co_day_auth_aggr:
209 co_day_auth_aggr[akc(repo.contact)] = {
210 co_day_auth_aggr[akc(repo.contact)] = {
210 "label": akc(repo.contact),
211 "label": akc(repo.contact),
211 "data": [0, 1],
212 "data": [0, 1],
212 "schema": ["commits"],
213 "schema": ["commits"],
213 }
214 }
214
215
215 stats = cur_stats if cur_stats else Statistics()
216 stats = cur_stats if cur_stats else Statistics()
216 stats.commit_activity = json.dumps(co_day_auth_aggr)
217 stats.commit_activity = json.dumps(co_day_auth_aggr)
217 stats.commit_activity_combined = json.dumps(overview_data)
218 stats.commit_activity_combined = json.dumps(overview_data)
218
219
219 log.debug('last revison %s', last_rev)
220 log.debug('last revison %s', last_rev)
220 leftovers = len(repo.revisions[last_rev:])
221 leftovers = len(repo.revisions[last_rev:])
221 log.debug('revisions to parse %s', leftovers)
222 log.debug('revisions to parse %s', leftovers)
222
223
223 if last_rev == 0 or leftovers < parse_limit:
224 if last_rev == 0 or leftovers < parse_limit:
224 log.debug('getting code trending stats')
225 log.debug('getting code trending stats')
225 stats.languages = json.dumps(__get_codes_stats(repo_name))
226 stats.languages = json.dumps(__get_codes_stats(repo_name))
226
227
227 try:
228 try:
228 stats.repository = dbrepo
229 stats.repository = dbrepo
229 stats.stat_on_revision = last_cs.revision if last_cs else 0
230 stats.stat_on_revision = last_cs.revision if last_cs else 0
230 sa.add(stats)
231 sa.add(stats)
231 sa.commit()
232 sa.commit()
232 except:
233 except:
233 log.error(traceback.format_exc())
234 log.error(traceback.format_exc())
234 sa.rollback()
235 sa.rollback()
235 lock.release()
236 lock.release()
236 return False
237 return False
237
238
238 #final release
239 #final release
239 lock.release()
240 lock.release()
240
241
241 #execute another task if celery is enabled
242 #execute another task if celery is enabled
242 if len(repo.revisions) > 1 and CELERY_ON:
243 if len(repo.revisions) > 1 and CELERY_ON:
243 run_task(get_commits_stats, repo_name, ts_min_y, ts_max_y)
244 run_task(get_commits_stats, repo_name, ts_min_y, ts_max_y)
244 return True
245 return True
245 except LockHeld:
246 except LockHeld:
246 log.info('LockHeld')
247 log.info('LockHeld')
247 return 'Task with key %s already running' % lockkey
248 return 'Task with key %s already running' % lockkey
248
249
249 @task(ignore_result=True)
250 @task(ignore_result=True)
250 def send_password_link(user_email):
251 def send_password_link(user_email):
251 try:
252 try:
252 log = reset_user_password.get_logger()
253 log = reset_user_password.get_logger()
253 except:
254 except:
254 log = logging.getLogger(__name__)
255 log = logging.getLogger(__name__)
255
256
256 from rhodecode.lib import auth
257 from rhodecode.lib import auth
257 from rhodecode.model.db import User
258 from rhodecode.model.db import User
258
259
259 try:
260 try:
260 sa = get_session()
261 sa = get_session()
261 user = sa.query(User).filter(User.email == user_email).scalar()
262 user = sa.query(User).filter(User.email == user_email).scalar()
262
263
263 if user:
264 if user:
264 link = url('reset_password_confirmation', key=user.api_key,
265 link = url('reset_password_confirmation', key=user.api_key,
265 qualified=True)
266 qualified=True)
266 tmpl = """
267 tmpl = """
267 Hello %s
268 Hello %s
268
269
269 We received a request to create a new password for your account.
270 We received a request to create a new password for your account.
270
271
271 You can generate it by clicking following URL:
272 You can generate it by clicking following URL:
272
273
273 %s
274 %s
274
275
275 If you didn't request new password please ignore this email.
276 If you didn't request new password please ignore this email.
276 """
277 """
277 run_task(send_email, user_email,
278 run_task(send_email, user_email,
278 "RhodeCode password reset link",
279 "RhodeCode password reset link",
279 tmpl % (user.short_contact, link))
280 tmpl % (user.short_contact, link))
280 log.info('send new password mail to %s', user_email)
281 log.info('send new password mail to %s', user_email)
281
282
282 except:
283 except:
283 log.error('Failed to update user password')
284 log.error('Failed to update user password')
284 log.error(traceback.format_exc())
285 log.error(traceback.format_exc())
285 return False
286 return False
286
287
287 return True
288 return True
288
289
289 @task(ignore_result=True)
290 @task(ignore_result=True)
290 def reset_user_password(user_email):
291 def reset_user_password(user_email):
291 try:
292 try:
292 log = reset_user_password.get_logger()
293 log = reset_user_password.get_logger()
293 except:
294 except:
294 log = logging.getLogger(__name__)
295 log = logging.getLogger(__name__)
295
296
296 from rhodecode.lib import auth
297 from rhodecode.lib import auth
297 from rhodecode.model.db import User
298 from rhodecode.model.db import User
298
299
299 try:
300 try:
300 try:
301 try:
301 sa = get_session()
302 sa = get_session()
302 user = sa.query(User).filter(User.email == user_email).scalar()
303 user = sa.query(User).filter(User.email == user_email).scalar()
303 new_passwd = auth.PasswordGenerator().gen_password(8,
304 new_passwd = auth.PasswordGenerator().gen_password(8,
304 auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
305 auth.PasswordGenerator.ALPHABETS_BIG_SMALL)
305 if user:
306 if user:
306 user.password = auth.get_crypt_password(new_passwd)
307 user.password = auth.get_crypt_password(new_passwd)
307 user.api_key = auth.generate_api_key(user.username)
308 user.api_key = auth.generate_api_key(user.username)
308 sa.add(user)
309 sa.add(user)
309 sa.commit()
310 sa.commit()
310 log.info('change password for %s', user_email)
311 log.info('change password for %s', user_email)
311 if new_passwd is None:
312 if new_passwd is None:
312 raise Exception('unable to generate new password')
313 raise Exception('unable to generate new password')
313
314
314 except:
315 except:
315 log.error(traceback.format_exc())
316 log.error(traceback.format_exc())
316 sa.rollback()
317 sa.rollback()
317
318
318 run_task(send_email, user_email,
319 run_task(send_email, user_email,
319 "Your new RhodeCode password",
320 "Your new RhodeCode password",
320 'Your new RhodeCode password:%s' % (new_passwd))
321 'Your new RhodeCode password:%s' % (new_passwd))
321 log.info('send new password mail to %s', user_email)
322 log.info('send new password mail to %s', user_email)
322
323
323 except:
324 except:
324 log.error('Failed to update user password')
325 log.error('Failed to update user password')
325 log.error(traceback.format_exc())
326 log.error(traceback.format_exc())
326
327
327 return True
328 return True
328
329
329
330
330 @task(ignore_result=True)
331 @task(ignore_result=True)
331 def send_email(recipients, subject, body):
332 def send_email(recipients, subject, body):
332 """
333 """
333 Sends an email with defined parameters from the .ini files.
334 Sends an email with defined parameters from the .ini files.
334
335
335 :param recipients: list of recipients, it this is empty the defined email
336 :param recipients: list of recipients, it this is empty the defined email
336 address from field 'email_to' is used instead
337 address from field 'email_to' is used instead
337 :param subject: subject of the mail
338 :param subject: subject of the mail
338 :param body: body of the mail
339 :param body: body of the mail
339 """
340 """
340 try:
341 try:
341 log = send_email.get_logger()
342 log = send_email.get_logger()
342 except:
343 except:
343 log = logging.getLogger(__name__)
344 log = logging.getLogger(__name__)
344
345
345 email_config = config
346 email_config = config
346
347
347 if not recipients:
348 if not recipients:
348 recipients = [email_config.get('email_to')]
349 recipients = [email_config.get('email_to')]
349
350
350 mail_from = email_config.get('app_email_from')
351 mail_from = email_config.get('app_email_from')
351 user = email_config.get('smtp_username')
352 user = email_config.get('smtp_username')
352 passwd = email_config.get('smtp_password')
353 passwd = email_config.get('smtp_password')
353 mail_server = email_config.get('smtp_server')
354 mail_server = email_config.get('smtp_server')
354 mail_port = email_config.get('smtp_port')
355 mail_port = email_config.get('smtp_port')
355 tls = str2bool(email_config.get('smtp_use_tls'))
356 tls = str2bool(email_config.get('smtp_use_tls'))
356 ssl = str2bool(email_config.get('smtp_use_ssl'))
357 ssl = str2bool(email_config.get('smtp_use_ssl'))
357 debug = str2bool(config.get('debug'))
358 debug = str2bool(config.get('debug'))
358
359
359 try:
360 try:
360 m = SmtpMailer(mail_from, user, passwd, mail_server,
361 m = SmtpMailer(mail_from, user, passwd, mail_server,
361 mail_port, ssl, tls, debug=debug)
362 mail_port, ssl, tls, debug=debug)
362 m.send(recipients, subject, body)
363 m.send(recipients, subject, body)
363 except:
364 except:
364 log.error('Mail sending failed')
365 log.error('Mail sending failed')
365 log.error(traceback.format_exc())
366 log.error(traceback.format_exc())
366 return False
367 return False
367 return True
368 return True
368
369
369
370
370 @task(ignore_result=True)
371 @task(ignore_result=True)
371 def create_repo_fork(form_data, cur_user):
372 def create_repo_fork(form_data, cur_user):
372 from rhodecode.model.repo import RepoModel
373 from rhodecode.model.repo import RepoModel
373 from vcs import get_backend
374 from vcs import get_backend
374
375
375 try:
376 try:
376 log = create_repo_fork.get_logger()
377 log = create_repo_fork.get_logger()
377 except:
378 except:
378 log = logging.getLogger(__name__)
379 log = logging.getLogger(__name__)
379
380
380 repo_model = RepoModel(get_session())
381 repo_model = RepoModel(get_session())
381 repo_model.create(form_data, cur_user, just_db=True, fork=True)
382 repo_model.create(form_data, cur_user, just_db=True, fork=True)
382 repo_name = form_data['repo_name']
383 repo_name = form_data['repo_name']
383 repos_path = get_repos_path()
384 repos_path = get_repos_path()
384 repo_path = os.path.join(repos_path, repo_name)
385 repo_path = os.path.join(repos_path, repo_name)
385 repo_fork_path = os.path.join(repos_path, form_data['fork_name'])
386 repo_fork_path = os.path.join(repos_path, form_data['fork_name'])
386 alias = form_data['repo_type']
387 alias = form_data['repo_type']
387
388
388 log.info('creating repo fork %s as %s', repo_name, repo_path)
389 log.info('creating repo fork %s as %s', repo_name, repo_path)
389 backend = get_backend(alias)
390 backend = get_backend(alias)
390 backend(str(repo_fork_path), create=True, src_url=str(repo_path))
391 backend(str(repo_fork_path), create=True, src_url=str(repo_path))
391
392
392
393
393 def __get_codes_stats(repo_name):
394 def __get_codes_stats(repo_name):
394 repos_path = get_repos_path()
395 repos_path = get_repos_path()
395 repo = get_repo(safe_str(os.path.join(repos_path, repo_name)))
396 repo = get_repo(safe_str(os.path.join(repos_path, repo_name)))
396 tip = repo.get_changeset()
397 tip = repo.get_changeset()
397 code_stats = {}
398 code_stats = {}
398
399
399 def aggregate(cs):
400 def aggregate(cs):
400 for f in cs[2]:
401 for f in cs[2]:
401 ext = lower(f.extension)
402 ext = lower(f.extension)
402 if ext in LANGUAGES_EXTENSIONS_MAP.keys() and not f.is_binary:
403 if ext in LANGUAGES_EXTENSIONS_MAP.keys() and not f.is_binary:
403 if ext in code_stats:
404 if ext in code_stats:
404 code_stats[ext] += 1
405 code_stats[ext] += 1
405 else:
406 else:
406 code_stats[ext] = 1
407 code_stats[ext] = 1
407
408
408 map(aggregate, tip.walk('/'))
409 map(aggregate, tip.walk('/'))
409
410
410 return code_stats or {}
411 return code_stats or {}
@@ -1,360 +1,379
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.compat
3 rhodecode.lib.compat
4 ~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~
5
5
6 Python backward compatibility functions and common libs
6 Python backward compatibility functions and common libs
7
7
8
8
9 :created_on: Oct 7, 2011
9 :created_on: Oct 7, 2011
10 :author: marcink
10 :author: marcink
11 :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
12 :license: GPLv3, see COPYING for more details.
12 :license: GPLv3, see COPYING for more details.
13 """
13 """
14 # This program is free software: you can redistribute it and/or modify
14 # This program is free software: you can redistribute it and/or modify
15 # it under the terms of the GNU General Public License as published by
15 # it under the terms of the GNU General Public License as published by
16 # the Free Software Foundation, either version 3 of the License, or
16 # the Free Software Foundation, either version 3 of the License, or
17 # (at your option) any later version.
17 # (at your option) any later version.
18 #
18 #
19 # This program is distributed in the hope that it will be useful,
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # GNU General Public License for more details.
22 # GNU General Public License for more details.
23 #
23 #
24 # You should have received a copy of the GNU General Public License
24 # You should have received a copy of the GNU General Public License
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26
26
27 import os
28 from rhodecode import __platform__, PLATFORM_WIN
29
27 #==============================================================================
30 #==============================================================================
28 # json
31 # json
29 #==============================================================================
32 #==============================================================================
30 try:
33 try:
31 import json
34 import json
32 except ImportError:
35 except ImportError:
33 import simplejson as json
36 import simplejson as json
34
37
35
38
36 #==============================================================================
39 #==============================================================================
37 # izip_longest
40 # izip_longest
38 #==============================================================================
41 #==============================================================================
39 try:
42 try:
40 from itertools import izip_longest
43 from itertools import izip_longest
41 except ImportError:
44 except ImportError:
42 import itertools
45 import itertools
43
46
44 def izip_longest(*args, **kwds): # noqa
47 def izip_longest(*args, **kwds): # noqa
45 fillvalue = kwds.get("fillvalue")
48 fillvalue = kwds.get("fillvalue")
46
49
47 def sentinel(counter=([fillvalue] * (len(args) - 1)).pop):
50 def sentinel(counter=([fillvalue] * (len(args) - 1)).pop):
48 yield counter() # yields the fillvalue, or raises IndexError
51 yield counter() # yields the fillvalue, or raises IndexError
49
52
50 fillers = itertools.repeat(fillvalue)
53 fillers = itertools.repeat(fillvalue)
51 iters = [itertools.chain(it, sentinel(), fillers)
54 iters = [itertools.chain(it, sentinel(), fillers)
52 for it in args]
55 for it in args]
53 try:
56 try:
54 for tup in itertools.izip(*iters):
57 for tup in itertools.izip(*iters):
55 yield tup
58 yield tup
56 except IndexError:
59 except IndexError:
57 pass
60 pass
58
61
59
62
60 #==============================================================================
63 #==============================================================================
61 # OrderedDict
64 # OrderedDict
62 #==============================================================================
65 #==============================================================================
63
66
64 # Python Software Foundation License
67 # Python Software Foundation License
65
68
66 # XXX: it feels like using the class with "is" and "is not" instead of "==" and
69 # XXX: it feels like using the class with "is" and "is not" instead of "==" and
67 # "!=" should be faster.
70 # "!=" should be faster.
68 class _Nil(object):
71 class _Nil(object):
69
72
70 def __repr__(self):
73 def __repr__(self):
71 return "nil"
74 return "nil"
72
75
73 def __eq__(self, other):
76 def __eq__(self, other):
74 if (isinstance(other, _Nil)):
77 if (isinstance(other, _Nil)):
75 return True
78 return True
76 else:
79 else:
77 return NotImplemented
80 return NotImplemented
78
81
79 def __ne__(self, other):
82 def __ne__(self, other):
80 if (isinstance(other, _Nil)):
83 if (isinstance(other, _Nil)):
81 return False
84 return False
82 else:
85 else:
83 return NotImplemented
86 return NotImplemented
84
87
85 _nil = _Nil()
88 _nil = _Nil()
86
89
87 class _odict(object):
90 class _odict(object):
88 """Ordered dict data structure, with O(1) complexity for dict operations
91 """Ordered dict data structure, with O(1) complexity for dict operations
89 that modify one element.
92 that modify one element.
90
93
91 Overwriting values doesn't change their original sequential order.
94 Overwriting values doesn't change their original sequential order.
92 """
95 """
93
96
94 def _dict_impl(self):
97 def _dict_impl(self):
95 return None
98 return None
96
99
97 def __init__(self, data=(), **kwds):
100 def __init__(self, data=(), **kwds):
98 """This doesn't accept keyword initialization as normal dicts to avoid
101 """This doesn't accept keyword initialization as normal dicts to avoid
99 a trap - inside a function or method the keyword args are accessible
102 a trap - inside a function or method the keyword args are accessible
100 only as a dict, without a defined order, so their original order is
103 only as a dict, without a defined order, so their original order is
101 lost.
104 lost.
102 """
105 """
103 if kwds:
106 if kwds:
104 raise TypeError("__init__() of ordered dict takes no keyword "
107 raise TypeError("__init__() of ordered dict takes no keyword "
105 "arguments to avoid an ordering trap.")
108 "arguments to avoid an ordering trap.")
106 self._dict_impl().__init__(self)
109 self._dict_impl().__init__(self)
107 # If you give a normal dict, then the order of elements is undefined
110 # If you give a normal dict, then the order of elements is undefined
108 if hasattr(data, "iteritems"):
111 if hasattr(data, "iteritems"):
109 for key, val in data.iteritems():
112 for key, val in data.iteritems():
110 self[key] = val
113 self[key] = val
111 else:
114 else:
112 for key, val in data:
115 for key, val in data:
113 self[key] = val
116 self[key] = val
114
117
115 # Double-linked list header
118 # Double-linked list header
116 def _get_lh(self):
119 def _get_lh(self):
117 dict_impl = self._dict_impl()
120 dict_impl = self._dict_impl()
118 if not hasattr(self, '_lh'):
121 if not hasattr(self, '_lh'):
119 dict_impl.__setattr__(self, '_lh', _nil)
122 dict_impl.__setattr__(self, '_lh', _nil)
120 return dict_impl.__getattribute__(self, '_lh')
123 return dict_impl.__getattribute__(self, '_lh')
121
124
122 def _set_lh(self, val):
125 def _set_lh(self, val):
123 self._dict_impl().__setattr__(self, '_lh', val)
126 self._dict_impl().__setattr__(self, '_lh', val)
124
127
125 lh = property(_get_lh, _set_lh)
128 lh = property(_get_lh, _set_lh)
126
129
127 # Double-linked list tail
130 # Double-linked list tail
128 def _get_lt(self):
131 def _get_lt(self):
129 dict_impl = self._dict_impl()
132 dict_impl = self._dict_impl()
130 if not hasattr(self, '_lt'):
133 if not hasattr(self, '_lt'):
131 dict_impl.__setattr__(self, '_lt', _nil)
134 dict_impl.__setattr__(self, '_lt', _nil)
132 return dict_impl.__getattribute__(self, '_lt')
135 return dict_impl.__getattribute__(self, '_lt')
133
136
134 def _set_lt(self, val):
137 def _set_lt(self, val):
135 self._dict_impl().__setattr__(self, '_lt', val)
138 self._dict_impl().__setattr__(self, '_lt', val)
136
139
137 lt = property(_get_lt, _set_lt)
140 lt = property(_get_lt, _set_lt)
138
141
139 def __getitem__(self, key):
142 def __getitem__(self, key):
140 return self._dict_impl().__getitem__(self, key)[1]
143 return self._dict_impl().__getitem__(self, key)[1]
141
144
142 def __setitem__(self, key, val):
145 def __setitem__(self, key, val):
143 dict_impl = self._dict_impl()
146 dict_impl = self._dict_impl()
144 try:
147 try:
145 dict_impl.__getitem__(self, key)[1] = val
148 dict_impl.__getitem__(self, key)[1] = val
146 except KeyError, e:
149 except KeyError, e:
147 new = [dict_impl.__getattribute__(self, 'lt'), val, _nil]
150 new = [dict_impl.__getattribute__(self, 'lt'), val, _nil]
148 dict_impl.__setitem__(self, key, new)
151 dict_impl.__setitem__(self, key, new)
149 if dict_impl.__getattribute__(self, 'lt') == _nil:
152 if dict_impl.__getattribute__(self, 'lt') == _nil:
150 dict_impl.__setattr__(self, 'lh', key)
153 dict_impl.__setattr__(self, 'lh', key)
151 else:
154 else:
152 dict_impl.__getitem__(
155 dict_impl.__getitem__(
153 self, dict_impl.__getattribute__(self, 'lt'))[2] = key
156 self, dict_impl.__getattribute__(self, 'lt'))[2] = key
154 dict_impl.__setattr__(self, 'lt', key)
157 dict_impl.__setattr__(self, 'lt', key)
155
158
156 def __delitem__(self, key):
159 def __delitem__(self, key):
157 dict_impl = self._dict_impl()
160 dict_impl = self._dict_impl()
158 pred, _ , succ = self._dict_impl().__getitem__(self, key)
161 pred, _ , succ = self._dict_impl().__getitem__(self, key)
159 if pred == _nil:
162 if pred == _nil:
160 dict_impl.__setattr__(self, 'lh', succ)
163 dict_impl.__setattr__(self, 'lh', succ)
161 else:
164 else:
162 dict_impl.__getitem__(self, pred)[2] = succ
165 dict_impl.__getitem__(self, pred)[2] = succ
163 if succ == _nil:
166 if succ == _nil:
164 dict_impl.__setattr__(self, 'lt', pred)
167 dict_impl.__setattr__(self, 'lt', pred)
165 else:
168 else:
166 dict_impl.__getitem__(self, succ)[0] = pred
169 dict_impl.__getitem__(self, succ)[0] = pred
167 dict_impl.__delitem__(self, key)
170 dict_impl.__delitem__(self, key)
168
171
169 def __contains__(self, key):
172 def __contains__(self, key):
170 return key in self.keys()
173 return key in self.keys()
171
174
172 def __len__(self):
175 def __len__(self):
173 return len(self.keys())
176 return len(self.keys())
174
177
175 def __str__(self):
178 def __str__(self):
176 pairs = ("%r: %r" % (k, v) for k, v in self.iteritems())
179 pairs = ("%r: %r" % (k, v) for k, v in self.iteritems())
177 return "{%s}" % ", ".join(pairs)
180 return "{%s}" % ", ".join(pairs)
178
181
179 def __repr__(self):
182 def __repr__(self):
180 if self:
183 if self:
181 pairs = ("(%r, %r)" % (k, v) for k, v in self.iteritems())
184 pairs = ("(%r, %r)" % (k, v) for k, v in self.iteritems())
182 return "odict([%s])" % ", ".join(pairs)
185 return "odict([%s])" % ", ".join(pairs)
183 else:
186 else:
184 return "odict()"
187 return "odict()"
185
188
186 def get(self, k, x=None):
189 def get(self, k, x=None):
187 if k in self:
190 if k in self:
188 return self._dict_impl().__getitem__(self, k)[1]
191 return self._dict_impl().__getitem__(self, k)[1]
189 else:
192 else:
190 return x
193 return x
191
194
192 def __iter__(self):
195 def __iter__(self):
193 dict_impl = self._dict_impl()
196 dict_impl = self._dict_impl()
194 curr_key = dict_impl.__getattribute__(self, 'lh')
197 curr_key = dict_impl.__getattribute__(self, 'lh')
195 while curr_key != _nil:
198 while curr_key != _nil:
196 yield curr_key
199 yield curr_key
197 curr_key = dict_impl.__getitem__(self, curr_key)[2]
200 curr_key = dict_impl.__getitem__(self, curr_key)[2]
198
201
199 iterkeys = __iter__
202 iterkeys = __iter__
200
203
201 def keys(self):
204 def keys(self):
202 return list(self.iterkeys())
205 return list(self.iterkeys())
203
206
204 def itervalues(self):
207 def itervalues(self):
205 dict_impl = self._dict_impl()
208 dict_impl = self._dict_impl()
206 curr_key = dict_impl.__getattribute__(self, 'lh')
209 curr_key = dict_impl.__getattribute__(self, 'lh')
207 while curr_key != _nil:
210 while curr_key != _nil:
208 _, val, curr_key = dict_impl.__getitem__(self, curr_key)
211 _, val, curr_key = dict_impl.__getitem__(self, curr_key)
209 yield val
212 yield val
210
213
211 def values(self):
214 def values(self):
212 return list(self.itervalues())
215 return list(self.itervalues())
213
216
214 def iteritems(self):
217 def iteritems(self):
215 dict_impl = self._dict_impl()
218 dict_impl = self._dict_impl()
216 curr_key = dict_impl.__getattribute__(self, 'lh')
219 curr_key = dict_impl.__getattribute__(self, 'lh')
217 while curr_key != _nil:
220 while curr_key != _nil:
218 _, val, next_key = dict_impl.__getitem__(self, curr_key)
221 _, val, next_key = dict_impl.__getitem__(self, curr_key)
219 yield curr_key, val
222 yield curr_key, val
220 curr_key = next_key
223 curr_key = next_key
221
224
222 def items(self):
225 def items(self):
223 return list(self.iteritems())
226 return list(self.iteritems())
224
227
225 def sort(self, cmp=None, key=None, reverse=False):
228 def sort(self, cmp=None, key=None, reverse=False):
226 items = [(k, v) for k, v in self.items()]
229 items = [(k, v) for k, v in self.items()]
227 if cmp is not None:
230 if cmp is not None:
228 items = sorted(items, cmp=cmp)
231 items = sorted(items, cmp=cmp)
229 elif key is not None:
232 elif key is not None:
230 items = sorted(items, key=key)
233 items = sorted(items, key=key)
231 else:
234 else:
232 items = sorted(items, key=lambda x: x[1])
235 items = sorted(items, key=lambda x: x[1])
233 if reverse:
236 if reverse:
234 items.reverse()
237 items.reverse()
235 self.clear()
238 self.clear()
236 self.__init__(items)
239 self.__init__(items)
237
240
238 def clear(self):
241 def clear(self):
239 dict_impl = self._dict_impl()
242 dict_impl = self._dict_impl()
240 dict_impl.clear(self)
243 dict_impl.clear(self)
241 dict_impl.__setattr__(self, 'lh', _nil)
244 dict_impl.__setattr__(self, 'lh', _nil)
242 dict_impl.__setattr__(self, 'lt', _nil)
245 dict_impl.__setattr__(self, 'lt', _nil)
243
246
244 def copy(self):
247 def copy(self):
245 return self.__class__(self)
248 return self.__class__(self)
246
249
247 def update(self, data=(), **kwds):
250 def update(self, data=(), **kwds):
248 if kwds:
251 if kwds:
249 raise TypeError("update() of ordered dict takes no keyword "
252 raise TypeError("update() of ordered dict takes no keyword "
250 "arguments to avoid an ordering trap.")
253 "arguments to avoid an ordering trap.")
251 if hasattr(data, "iteritems"):
254 if hasattr(data, "iteritems"):
252 data = data.iteritems()
255 data = data.iteritems()
253 for key, val in data:
256 for key, val in data:
254 self[key] = val
257 self[key] = val
255
258
256 def setdefault(self, k, x=None):
259 def setdefault(self, k, x=None):
257 try:
260 try:
258 return self[k]
261 return self[k]
259 except KeyError:
262 except KeyError:
260 self[k] = x
263 self[k] = x
261 return x
264 return x
262
265
263 def pop(self, k, x=_nil):
266 def pop(self, k, x=_nil):
264 try:
267 try:
265 val = self[k]
268 val = self[k]
266 del self[k]
269 del self[k]
267 return val
270 return val
268 except KeyError:
271 except KeyError:
269 if x == _nil:
272 if x == _nil:
270 raise
273 raise
271 return x
274 return x
272
275
273 def popitem(self):
276 def popitem(self):
274 try:
277 try:
275 dict_impl = self._dict_impl()
278 dict_impl = self._dict_impl()
276 key = dict_impl.__getattribute__(self, 'lt')
279 key = dict_impl.__getattribute__(self, 'lt')
277 return key, self.pop(key)
280 return key, self.pop(key)
278 except KeyError:
281 except KeyError:
279 raise KeyError("'popitem(): ordered dictionary is empty'")
282 raise KeyError("'popitem(): ordered dictionary is empty'")
280
283
281 def riterkeys(self):
284 def riterkeys(self):
282 """To iterate on keys in reversed order.
285 """To iterate on keys in reversed order.
283 """
286 """
284 dict_impl = self._dict_impl()
287 dict_impl = self._dict_impl()
285 curr_key = dict_impl.__getattribute__(self, 'lt')
288 curr_key = dict_impl.__getattribute__(self, 'lt')
286 while curr_key != _nil:
289 while curr_key != _nil:
287 yield curr_key
290 yield curr_key
288 curr_key = dict_impl.__getitem__(self, curr_key)[0]
291 curr_key = dict_impl.__getitem__(self, curr_key)[0]
289
292
290 __reversed__ = riterkeys
293 __reversed__ = riterkeys
291
294
292 def rkeys(self):
295 def rkeys(self):
293 """List of the keys in reversed order.
296 """List of the keys in reversed order.
294 """
297 """
295 return list(self.riterkeys())
298 return list(self.riterkeys())
296
299
297 def ritervalues(self):
300 def ritervalues(self):
298 """To iterate on values in reversed order.
301 """To iterate on values in reversed order.
299 """
302 """
300 dict_impl = self._dict_impl()
303 dict_impl = self._dict_impl()
301 curr_key = dict_impl.__getattribute__(self, 'lt')
304 curr_key = dict_impl.__getattribute__(self, 'lt')
302 while curr_key != _nil:
305 while curr_key != _nil:
303 curr_key, val, _ = dict_impl.__getitem__(self, curr_key)
306 curr_key, val, _ = dict_impl.__getitem__(self, curr_key)
304 yield val
307 yield val
305
308
306 def rvalues(self):
309 def rvalues(self):
307 """List of the values in reversed order.
310 """List of the values in reversed order.
308 """
311 """
309 return list(self.ritervalues())
312 return list(self.ritervalues())
310
313
311 def riteritems(self):
314 def riteritems(self):
312 """To iterate on (key, value) in reversed order.
315 """To iterate on (key, value) in reversed order.
313 """
316 """
314 dict_impl = self._dict_impl()
317 dict_impl = self._dict_impl()
315 curr_key = dict_impl.__getattribute__(self, 'lt')
318 curr_key = dict_impl.__getattribute__(self, 'lt')
316 while curr_key != _nil:
319 while curr_key != _nil:
317 pred_key, val, _ = dict_impl.__getitem__(self, curr_key)
320 pred_key, val, _ = dict_impl.__getitem__(self, curr_key)
318 yield curr_key, val
321 yield curr_key, val
319 curr_key = pred_key
322 curr_key = pred_key
320
323
321 def ritems(self):
324 def ritems(self):
322 """List of the (key, value) in reversed order.
325 """List of the (key, value) in reversed order.
323 """
326 """
324 return list(self.riteritems())
327 return list(self.riteritems())
325
328
326 def firstkey(self):
329 def firstkey(self):
327 if self:
330 if self:
328 return self._dict_impl().__getattribute__(self, 'lh')
331 return self._dict_impl().__getattribute__(self, 'lh')
329 else:
332 else:
330 raise KeyError("'firstkey(): ordered dictionary is empty'")
333 raise KeyError("'firstkey(): ordered dictionary is empty'")
331
334
332 def lastkey(self):
335 def lastkey(self):
333 if self:
336 if self:
334 return self._dict_impl().__getattribute__(self, 'lt')
337 return self._dict_impl().__getattribute__(self, 'lt')
335 else:
338 else:
336 raise KeyError("'lastkey(): ordered dictionary is empty'")
339 raise KeyError("'lastkey(): ordered dictionary is empty'")
337
340
338 def as_dict(self):
341 def as_dict(self):
339 return self._dict_impl()(self.items())
342 return self._dict_impl()(self.items())
340
343
341 def _repr(self):
344 def _repr(self):
342 """_repr(): low level repr of the whole data contained in the odict.
345 """_repr(): low level repr of the whole data contained in the odict.
343 Useful for debugging.
346 Useful for debugging.
344 """
347 """
345 dict_impl = self._dict_impl()
348 dict_impl = self._dict_impl()
346 form = "odict low level repr lh,lt,data: %r, %r, %s"
349 form = "odict low level repr lh,lt,data: %r, %r, %s"
347 return form % (dict_impl.__getattribute__(self, 'lh'),
350 return form % (dict_impl.__getattribute__(self, 'lh'),
348 dict_impl.__getattribute__(self, 'lt'),
351 dict_impl.__getattribute__(self, 'lt'),
349 dict_impl.__repr__(self))
352 dict_impl.__repr__(self))
350
353
351 class OrderedDict(_odict, dict):
354 class OrderedDict(_odict, dict):
352
355
353 def _dict_impl(self):
356 def _dict_impl(self):
354 return dict
357 return dict
355
358
356
359
357 #==============================================================================
360 #==============================================================================
358 # OrderedSet
361 # OrderedSet
359 #==============================================================================
362 #==============================================================================
360 from sqlalchemy.util import OrderedSet
363 from sqlalchemy.util import OrderedSet
364
365
366 #==============================================================================
367 # kill FUNCTIONS
368 #==============================================================================
369 if __platform__ in PLATFORM_WIN:
370 import ctypes
371
372 def kill(pid, sig):
373 """kill function for Win32"""
374 kernel32 = ctypes.windll.kernel32
375 handle = kernel32.OpenProcess(1, 0, pid)
376 return (0 != kernel32.TerminateProcess(handle, 0))
377
378 else:
379 kill = os.kill
@@ -1,50 +1,50
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.exceptions
3 rhodecode.lib.exceptions
4 ~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Set of custom exceptions used in RhodeCode
6 Set of custom exceptions used in RhodeCode
7
7
8 :created_on: Nov 17, 2010
8 :created_on: Nov 17, 2010
9 :copyright: (c) 2010 by marcink.
9 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :license: LICENSE_NAME, see LICENSE_FILE for more details.
10 :license: GPLv3, see COPYING for more details.
11 """
11 """
12 # This program is free software: you can redistribute it and/or modify
12 # This program is free software: you can redistribute it and/or modify
13 # it under the terms of the GNU General Public License as published by
13 # it under the terms of the GNU General Public License as published by
14 # the Free Software Foundation, either version 3 of the License, or
14 # the Free Software Foundation, either version 3 of the License, or
15 # (at your option) any later version.
15 # (at your option) any later version.
16 #
16 #
17 # This program is distributed in the hope that it will be useful,
17 # This program is distributed in the hope that it will be useful,
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # GNU General Public License for more details.
20 # GNU General Public License for more details.
21 #
21 #
22 # You should have received a copy of the GNU General Public License
22 # You should have received a copy of the GNU General Public License
23 # along with this program. If not, see <http://www.gnu.org/licenses/>.
23 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24
24
25
25
26 class LdapUsernameError(Exception):
26 class LdapUsernameError(Exception):
27 pass
27 pass
28
28
29
29
30 class LdapPasswordError(Exception):
30 class LdapPasswordError(Exception):
31 pass
31 pass
32
32
33
33
34 class LdapConnectionError(Exception):
34 class LdapConnectionError(Exception):
35 pass
35 pass
36
36
37
37
38 class LdapImportError(Exception):
38 class LdapImportError(Exception):
39 pass
39 pass
40
40
41
41
42 class DefaultUserException(Exception):
42 class DefaultUserException(Exception):
43 pass
43 pass
44
44
45
45
46 class UserOwnsReposException(Exception):
46 class UserOwnsReposException(Exception):
47 pass
47 pass
48
48
49 class UsersGroupsAssignedException(Exception):
49 class UsersGroupsAssignedException(Exception):
50 pass
50 pass
@@ -1,673 +1,672
1 """Helper functions
1 """Helper functions
2
2
3 Consists of functions to typically be used within templates, but also
3 Consists of functions to typically be used within templates, but also
4 available to Controllers. This module is available to both as 'h'.
4 available to Controllers. This module is available to both as 'h'.
5 """
5 """
6 import random
6 import random
7 import hashlib
7 import hashlib
8 import StringIO
8 import StringIO
9 import urllib
9 import urllib
10 import math
10 import math
11
11
12 from datetime import datetime
12 from datetime import datetime
13 from pygments.formatters import HtmlFormatter
13 from pygments.formatters import HtmlFormatter
14 from pygments import highlight as code_highlight
14 from pygments import highlight as code_highlight
15 from pylons import url, request, config
15 from pylons import url, request, config
16 from pylons.i18n.translation import _, ungettext
16 from pylons.i18n.translation import _, ungettext
17
17
18 from webhelpers.html import literal, HTML, escape
18 from webhelpers.html import literal, HTML, escape
19 from webhelpers.html.tools import *
19 from webhelpers.html.tools import *
20 from webhelpers.html.builder import make_tag
20 from webhelpers.html.builder import make_tag
21 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
21 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
22 end_form, file, form, hidden, image, javascript_link, link_to, link_to_if, \
22 end_form, file, form, hidden, image, javascript_link, link_to, link_to_if, \
23 link_to_unless, ol, required_legend, select, stylesheet_link, submit, text, \
23 link_to_unless, ol, required_legend, select, stylesheet_link, submit, text, \
24 password, textarea, title, ul, xml_declaration, radio
24 password, textarea, title, ul, xml_declaration, radio
25 from webhelpers.html.tools import auto_link, button_to, highlight, js_obfuscate, \
25 from webhelpers.html.tools import auto_link, button_to, highlight, js_obfuscate, \
26 mail_to, strip_links, strip_tags, tag_re
26 mail_to, strip_links, strip_tags, tag_re
27 from webhelpers.number import format_byte_size, format_bit_size
27 from webhelpers.number import format_byte_size, format_bit_size
28 from webhelpers.pylonslib import Flash as _Flash
28 from webhelpers.pylonslib import Flash as _Flash
29 from webhelpers.pylonslib.secure_form import secure_form
29 from webhelpers.pylonslib.secure_form import secure_form
30 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
30 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
31 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
31 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
32 replace_whitespace, urlify, truncate, wrap_paragraphs
32 replace_whitespace, urlify, truncate, wrap_paragraphs
33 from webhelpers.date import time_ago_in_words
33 from webhelpers.date import time_ago_in_words
34 from webhelpers.paginate import Page
34 from webhelpers.paginate import Page
35 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
35 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
36 convert_boolean_attrs, NotGiven
36 convert_boolean_attrs, NotGiven
37
37
38 from vcs.utils.annotate import annotate_highlight
38 from vcs.utils.annotate import annotate_highlight
39 from rhodecode.lib.utils import repo_name_slug
39 from rhodecode.lib.utils import repo_name_slug
40 from rhodecode.lib import str2bool, safe_unicode, safe_str,get_changeset_safe
40 from rhodecode.lib import str2bool, safe_unicode, safe_str,get_changeset_safe
41
41
42 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
42 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
43 """
43 """
44 Reset button
44 Reset button
45 """
45 """
46 _set_input_attrs(attrs, type, name, value)
46 _set_input_attrs(attrs, type, name, value)
47 _set_id_attr(attrs, id, name)
47 _set_id_attr(attrs, id, name)
48 convert_boolean_attrs(attrs, ["disabled"])
48 convert_boolean_attrs(attrs, ["disabled"])
49 return HTML.input(**attrs)
49 return HTML.input(**attrs)
50
50
51 reset = _reset
51 reset = _reset
52
52
53
53
54 def get_token():
54 def get_token():
55 """Return the current authentication token, creating one if one doesn't
55 """Return the current authentication token, creating one if one doesn't
56 already exist.
56 already exist.
57 """
57 """
58 token_key = "_authentication_token"
58 token_key = "_authentication_token"
59 from pylons import session
59 from pylons import session
60 if not token_key in session:
60 if not token_key in session:
61 try:
61 try:
62 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
62 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
63 except AttributeError: # Python < 2.4
63 except AttributeError: # Python < 2.4
64 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
64 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
65 session[token_key] = token
65 session[token_key] = token
66 if hasattr(session, 'save'):
66 if hasattr(session, 'save'):
67 session.save()
67 session.save()
68 return session[token_key]
68 return session[token_key]
69
69
70 class _GetError(object):
70 class _GetError(object):
71 """Get error from form_errors, and represent it as span wrapped error
71 """Get error from form_errors, and represent it as span wrapped error
72 message
72 message
73
73
74 :param field_name: field to fetch errors for
74 :param field_name: field to fetch errors for
75 :param form_errors: form errors dict
75 :param form_errors: form errors dict
76 """
76 """
77
77
78 def __call__(self, field_name, form_errors):
78 def __call__(self, field_name, form_errors):
79 tmpl = """<span class="error_msg">%s</span>"""
79 tmpl = """<span class="error_msg">%s</span>"""
80 if form_errors and form_errors.has_key(field_name):
80 if form_errors and form_errors.has_key(field_name):
81 return literal(tmpl % form_errors.get(field_name))
81 return literal(tmpl % form_errors.get(field_name))
82
82
83 get_error = _GetError()
83 get_error = _GetError()
84
84
85 class _ToolTip(object):
85 class _ToolTip(object):
86
86
87 def __call__(self, tooltip_title, trim_at=50):
87 def __call__(self, tooltip_title, trim_at=50):
88 """Special function just to wrap our text into nice formatted
88 """Special function just to wrap our text into nice formatted
89 autowrapped text
89 autowrapped text
90
90
91 :param tooltip_title:
91 :param tooltip_title:
92 """
92 """
93 return escape(tooltip_title)
93 return escape(tooltip_title)
94 tooltip = _ToolTip()
94 tooltip = _ToolTip()
95
95
96 class _FilesBreadCrumbs(object):
96 class _FilesBreadCrumbs(object):
97
97
98 def __call__(self, repo_name, rev, paths):
98 def __call__(self, repo_name, rev, paths):
99 if isinstance(paths, str):
99 if isinstance(paths, str):
100 paths = safe_unicode(paths)
100 paths = safe_unicode(paths)
101 url_l = [link_to(repo_name, url('files_home',
101 url_l = [link_to(repo_name, url('files_home',
102 repo_name=repo_name,
102 repo_name=repo_name,
103 revision=rev, f_path=''))]
103 revision=rev, f_path=''))]
104 paths_l = paths.split('/')
104 paths_l = paths.split('/')
105 for cnt, p in enumerate(paths_l):
105 for cnt, p in enumerate(paths_l):
106 if p != '':
106 if p != '':
107 url_l.append(link_to(p, url('files_home',
107 url_l.append(link_to(p, url('files_home',
108 repo_name=repo_name,
108 repo_name=repo_name,
109 revision=rev,
109 revision=rev,
110 f_path='/'.join(paths_l[:cnt + 1]))))
110 f_path='/'.join(paths_l[:cnt + 1]))))
111
111
112 return literal('/'.join(url_l))
112 return literal('/'.join(url_l))
113
113
114 files_breadcrumbs = _FilesBreadCrumbs()
114 files_breadcrumbs = _FilesBreadCrumbs()
115
115
116 class CodeHtmlFormatter(HtmlFormatter):
116 class CodeHtmlFormatter(HtmlFormatter):
117 """My code Html Formatter for source codes
117 """My code Html Formatter for source codes
118 """
118 """
119
119
120 def wrap(self, source, outfile):
120 def wrap(self, source, outfile):
121 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
121 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
122
122
123 def _wrap_code(self, source):
123 def _wrap_code(self, source):
124 for cnt, it in enumerate(source):
124 for cnt, it in enumerate(source):
125 i, t = it
125 i, t = it
126 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
126 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
127 yield i, t
127 yield i, t
128
128
129 def _wrap_tablelinenos(self, inner):
129 def _wrap_tablelinenos(self, inner):
130 dummyoutfile = StringIO.StringIO()
130 dummyoutfile = StringIO.StringIO()
131 lncount = 0
131 lncount = 0
132 for t, line in inner:
132 for t, line in inner:
133 if t:
133 if t:
134 lncount += 1
134 lncount += 1
135 dummyoutfile.write(line)
135 dummyoutfile.write(line)
136
136
137 fl = self.linenostart
137 fl = self.linenostart
138 mw = len(str(lncount + fl - 1))
138 mw = len(str(lncount + fl - 1))
139 sp = self.linenospecial
139 sp = self.linenospecial
140 st = self.linenostep
140 st = self.linenostep
141 la = self.lineanchors
141 la = self.lineanchors
142 aln = self.anchorlinenos
142 aln = self.anchorlinenos
143 nocls = self.noclasses
143 nocls = self.noclasses
144 if sp:
144 if sp:
145 lines = []
145 lines = []
146
146
147 for i in range(fl, fl + lncount):
147 for i in range(fl, fl + lncount):
148 if i % st == 0:
148 if i % st == 0:
149 if i % sp == 0:
149 if i % sp == 0:
150 if aln:
150 if aln:
151 lines.append('<a href="#%s%d" class="special">%*d</a>' %
151 lines.append('<a href="#%s%d" class="special">%*d</a>' %
152 (la, i, mw, i))
152 (la, i, mw, i))
153 else:
153 else:
154 lines.append('<span class="special">%*d</span>' % (mw, i))
154 lines.append('<span class="special">%*d</span>' % (mw, i))
155 else:
155 else:
156 if aln:
156 if aln:
157 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
157 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
158 else:
158 else:
159 lines.append('%*d' % (mw, i))
159 lines.append('%*d' % (mw, i))
160 else:
160 else:
161 lines.append('')
161 lines.append('')
162 ls = '\n'.join(lines)
162 ls = '\n'.join(lines)
163 else:
163 else:
164 lines = []
164 lines = []
165 for i in range(fl, fl + lncount):
165 for i in range(fl, fl + lncount):
166 if i % st == 0:
166 if i % st == 0:
167 if aln:
167 if aln:
168 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
168 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
169 else:
169 else:
170 lines.append('%*d' % (mw, i))
170 lines.append('%*d' % (mw, i))
171 else:
171 else:
172 lines.append('')
172 lines.append('')
173 ls = '\n'.join(lines)
173 ls = '\n'.join(lines)
174
174
175 # in case you wonder about the seemingly redundant <div> here: since the
175 # in case you wonder about the seemingly redundant <div> here: since the
176 # content in the other cell also is wrapped in a div, some browsers in
176 # content in the other cell also is wrapped in a div, some browsers in
177 # some configurations seem to mess up the formatting...
177 # some configurations seem to mess up the formatting...
178 if nocls:
178 if nocls:
179 yield 0, ('<table class="%stable">' % self.cssclass +
179 yield 0, ('<table class="%stable">' % self.cssclass +
180 '<tr><td><div class="linenodiv" '
180 '<tr><td><div class="linenodiv" '
181 'style="background-color: #f0f0f0; padding-right: 10px">'
181 'style="background-color: #f0f0f0; padding-right: 10px">'
182 '<pre style="line-height: 125%">' +
182 '<pre style="line-height: 125%">' +
183 ls + '</pre></div></td><td id="hlcode" class="code">')
183 ls + '</pre></div></td><td id="hlcode" class="code">')
184 else:
184 else:
185 yield 0, ('<table class="%stable">' % self.cssclass +
185 yield 0, ('<table class="%stable">' % self.cssclass +
186 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
186 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
187 ls + '</pre></div></td><td id="hlcode" class="code">')
187 ls + '</pre></div></td><td id="hlcode" class="code">')
188 yield 0, dummyoutfile.getvalue()
188 yield 0, dummyoutfile.getvalue()
189 yield 0, '</td></tr></table>'
189 yield 0, '</td></tr></table>'
190
190
191
191
192 def pygmentize(filenode, **kwargs):
192 def pygmentize(filenode, **kwargs):
193 """pygmentize function using pygments
193 """pygmentize function using pygments
194
194
195 :param filenode:
195 :param filenode:
196 """
196 """
197
197
198 return literal(code_highlight(filenode.content,
198 return literal(code_highlight(filenode.content,
199 filenode.lexer, CodeHtmlFormatter(**kwargs)))
199 filenode.lexer, CodeHtmlFormatter(**kwargs)))
200
200
201 def pygmentize_annotation(repo_name, filenode, **kwargs):
201 def pygmentize_annotation(repo_name, filenode, **kwargs):
202 """pygmentize function for annotation
202 """pygmentize function for annotation
203
203
204 :param filenode:
204 :param filenode:
205 """
205 """
206
206
207 color_dict = {}
207 color_dict = {}
208 def gen_color(n=10000):
208 def gen_color(n=10000):
209 """generator for getting n of evenly distributed colors using
209 """generator for getting n of evenly distributed colors using
210 hsv color and golden ratio. It always return same order of colors
210 hsv color and golden ratio. It always return same order of colors
211
211
212 :returns: RGB tuple
212 :returns: RGB tuple
213 """
213 """
214
214
215 def hsv_to_rgb(h, s, v):
215 def hsv_to_rgb(h, s, v):
216 if s == 0.0: return v, v, v
216 if s == 0.0: return v, v, v
217 i = int(h * 6.0) # XXX assume int() truncates!
217 i = int(h * 6.0) # XXX assume int() truncates!
218 f = (h * 6.0) - i
218 f = (h * 6.0) - i
219 p = v * (1.0 - s)
219 p = v * (1.0 - s)
220 q = v * (1.0 - s * f)
220 q = v * (1.0 - s * f)
221 t = v * (1.0 - s * (1.0 - f))
221 t = v * (1.0 - s * (1.0 - f))
222 i = i % 6
222 i = i % 6
223 if i == 0: return v, t, p
223 if i == 0: return v, t, p
224 if i == 1: return q, v, p
224 if i == 1: return q, v, p
225 if i == 2: return p, v, t
225 if i == 2: return p, v, t
226 if i == 3: return p, q, v
226 if i == 3: return p, q, v
227 if i == 4: return t, p, v
227 if i == 4: return t, p, v
228 if i == 5: return v, p, q
228 if i == 5: return v, p, q
229
229
230 golden_ratio = 0.618033988749895
230 golden_ratio = 0.618033988749895
231 h = 0.22717784590367374
231 h = 0.22717784590367374
232
232
233 for _ in xrange(n):
233 for _ in xrange(n):
234 h += golden_ratio
234 h += golden_ratio
235 h %= 1
235 h %= 1
236 HSV_tuple = [h, 0.95, 0.95]
236 HSV_tuple = [h, 0.95, 0.95]
237 RGB_tuple = hsv_to_rgb(*HSV_tuple)
237 RGB_tuple = hsv_to_rgb(*HSV_tuple)
238 yield map(lambda x:str(int(x * 256)), RGB_tuple)
238 yield map(lambda x:str(int(x * 256)), RGB_tuple)
239
239
240 cgenerator = gen_color()
240 cgenerator = gen_color()
241
241
242 def get_color_string(cs):
242 def get_color_string(cs):
243 if color_dict.has_key(cs):
243 if color_dict.has_key(cs):
244 col = color_dict[cs]
244 col = color_dict[cs]
245 else:
245 else:
246 col = color_dict[cs] = cgenerator.next()
246 col = color_dict[cs] = cgenerator.next()
247 return "color: rgb(%s)! important;" % (', '.join(col))
247 return "color: rgb(%s)! important;" % (', '.join(col))
248
248
249 def url_func(repo_name):
249 def url_func(repo_name):
250
250
251 def _url_func(changeset):
251 def _url_func(changeset):
252 author = changeset.author
252 author = changeset.author
253 date = changeset.date
253 date = changeset.date
254 message = tooltip(changeset.message)
254 message = tooltip(changeset.message)
255
255
256 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
256 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
257 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
257 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
258 "</b> %s<br/></div>")
258 "</b> %s<br/></div>")
259
259
260 tooltip_html = tooltip_html % (author, date, message)
260 tooltip_html = tooltip_html % (author, date, message)
261 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
261 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
262 short_id(changeset.raw_id))
262 short_id(changeset.raw_id))
263 uri = link_to(
263 uri = link_to(
264 lnk_format,
264 lnk_format,
265 url('changeset_home', repo_name=repo_name,
265 url('changeset_home', repo_name=repo_name,
266 revision=changeset.raw_id),
266 revision=changeset.raw_id),
267 style=get_color_string(changeset.raw_id),
267 style=get_color_string(changeset.raw_id),
268 class_='tooltip',
268 class_='tooltip',
269 title=tooltip_html
269 title=tooltip_html
270 )
270 )
271
271
272 uri += '\n'
272 uri += '\n'
273 return uri
273 return uri
274 return _url_func
274 return _url_func
275
275
276 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
276 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
277
277
278 def is_following_repo(repo_name, user_id):
278 def is_following_repo(repo_name, user_id):
279 from rhodecode.model.scm import ScmModel
279 from rhodecode.model.scm import ScmModel
280 return ScmModel().is_following_repo(repo_name, user_id)
280 return ScmModel().is_following_repo(repo_name, user_id)
281
281
282 flash = _Flash()
282 flash = _Flash()
283
283
284 #==============================================================================
284 #==============================================================================
285 # SCM FILTERS available via h.
285 # SCM FILTERS available via h.
286 #==============================================================================
286 #==============================================================================
287 from vcs.utils import author_name, author_email
287 from vcs.utils import author_name, author_email
288 from rhodecode.lib import credentials_filter, age as _age
288 from rhodecode.lib import credentials_filter, age as _age
289
289
290 age = lambda x:_age(x)
290 age = lambda x:_age(x)
291 capitalize = lambda x: x.capitalize()
291 capitalize = lambda x: x.capitalize()
292 email = author_email
292 email = author_email
293 email_or_none = lambda x: email(x) if email(x) != x else None
293 email_or_none = lambda x: email(x) if email(x) != x else None
294 person = lambda x: author_name(x)
294 person = lambda x: author_name(x)
295 short_id = lambda x: x[:12]
295 short_id = lambda x: x[:12]
296 hide_credentials = lambda x: ''.join(credentials_filter(x))
296 hide_credentials = lambda x: ''.join(credentials_filter(x))
297
297
298 def bool2icon(value):
298 def bool2icon(value):
299 """Returns True/False values represented as small html image of true/false
299 """Returns True/False values represented as small html image of true/false
300 icons
300 icons
301
301
302 :param value: bool value
302 :param value: bool value
303 """
303 """
304
304
305 if value is True:
305 if value is True:
306 return HTML.tag('img', src=url("/images/icons/accept.png"),
306 return HTML.tag('img', src=url("/images/icons/accept.png"),
307 alt=_('True'))
307 alt=_('True'))
308
308
309 if value is False:
309 if value is False:
310 return HTML.tag('img', src=url("/images/icons/cancel.png"),
310 return HTML.tag('img', src=url("/images/icons/cancel.png"),
311 alt=_('False'))
311 alt=_('False'))
312
312
313 return value
313 return value
314
314
315
315
316 def action_parser(user_log, feed=False):
316 def action_parser(user_log, feed=False):
317 """This helper will action_map the specified string action into translated
317 """This helper will action_map the specified string action into translated
318 fancy names with icons and links
318 fancy names with icons and links
319
319
320 :param user_log: user log instance
320 :param user_log: user log instance
321 :param feed: use output for feeds (no html and fancy icons)
321 :param feed: use output for feeds (no html and fancy icons)
322 """
322 """
323
323
324 action = user_log.action
324 action = user_log.action
325 action_params = ' '
325 action_params = ' '
326
326
327 x = action.split(':')
327 x = action.split(':')
328
328
329 if len(x) > 1:
329 if len(x) > 1:
330 action, action_params = x
330 action, action_params = x
331
331
332 def get_cs_links():
332 def get_cs_links():
333 revs_limit = 3 #display this amount always
333 revs_limit = 3 #display this amount always
334 revs_top_limit = 50 #show upto this amount of changesets hidden
334 revs_top_limit = 50 #show upto this amount of changesets hidden
335 revs = action_params.split(',')
335 revs = action_params.split(',')
336 repo_name = user_log.repository.repo_name
336 repo_name = user_log.repository.repo_name
337
337
338 from rhodecode.model.scm import ScmModel
338 from rhodecode.model.scm import ScmModel
339 repo = user_log.repository.scm_instance
339 repo = user_log.repository.scm_instance
340
340
341 message = lambda rev: get_changeset_safe(repo, rev).message
341 message = lambda rev: get_changeset_safe(repo, rev).message
342 cs_links = []
342 cs_links = []
343 cs_links.append(" " + ', '.join ([link_to(rev,
343 cs_links.append(" " + ', '.join ([link_to(rev,
344 url('changeset_home',
344 url('changeset_home',
345 repo_name=repo_name,
345 repo_name=repo_name,
346 revision=rev), title=tooltip(message(rev)),
346 revision=rev), title=tooltip(message(rev)),
347 class_='tooltip') for rev in revs[:revs_limit] ]))
347 class_='tooltip') for rev in revs[:revs_limit] ]))
348
348
349 compare_view = (' <div class="compare_view tooltip" title="%s">'
349 compare_view = (' <div class="compare_view tooltip" title="%s">'
350 '<a href="%s">%s</a> '
350 '<a href="%s">%s</a> '
351 '</div>' % (_('Show all combined changesets %s->%s' \
351 '</div>' % (_('Show all combined changesets %s->%s' \
352 % (revs[0], revs[-1])),
352 % (revs[0], revs[-1])),
353 url('changeset_home', repo_name=repo_name,
353 url('changeset_home', repo_name=repo_name,
354 revision='%s...%s' % (revs[0], revs[-1])
354 revision='%s...%s' % (revs[0], revs[-1])
355 ),
355 ),
356 _('compare view'))
356 _('compare view'))
357 )
357 )
358
358
359 if len(revs) > revs_limit:
359 if len(revs) > revs_limit:
360 uniq_id = revs[0]
360 uniq_id = revs[0]
361 html_tmpl = ('<span> %s '
361 html_tmpl = ('<span> %s '
362 '<a class="show_more" id="_%s" href="#more">%s</a> '
362 '<a class="show_more" id="_%s" href="#more">%s</a> '
363 '%s</span>')
363 '%s</span>')
364 if not feed:
364 if not feed:
365 cs_links.append(html_tmpl % (_('and'), uniq_id, _('%s more') \
365 cs_links.append(html_tmpl % (_('and'), uniq_id, _('%s more') \
366 % (len(revs) - revs_limit),
366 % (len(revs) - revs_limit),
367 _('revisions')))
367 _('revisions')))
368
368
369 if not feed:
369 if not feed:
370 html_tmpl = '<span id="%s" style="display:none"> %s </span>'
370 html_tmpl = '<span id="%s" style="display:none"> %s </span>'
371 else:
371 else:
372 html_tmpl = '<span id="%s"> %s </span>'
372 html_tmpl = '<span id="%s"> %s </span>'
373
373
374 cs_links.append(html_tmpl % (uniq_id, ', '.join([link_to(rev,
374 cs_links.append(html_tmpl % (uniq_id, ', '.join([link_to(rev,
375 url('changeset_home',
375 url('changeset_home',
376 repo_name=repo_name, revision=rev),
376 repo_name=repo_name, revision=rev),
377 title=message(rev), class_='tooltip')
377 title=message(rev), class_='tooltip')
378 for rev in revs[revs_limit:revs_top_limit]])))
378 for rev in revs[revs_limit:revs_top_limit]])))
379 if len(revs) > 1:
379 if len(revs) > 1:
380 cs_links.append(compare_view)
380 cs_links.append(compare_view)
381 return ''.join(cs_links)
381 return ''.join(cs_links)
382
382
383 def get_fork_name():
383 def get_fork_name():
384 repo_name = action_params
384 repo_name = action_params
385 return _('fork name ') + str(link_to(action_params, url('summary_home',
385 return _('fork name ') + str(link_to(action_params, url('summary_home',
386 repo_name=repo_name,)))
386 repo_name=repo_name,)))
387
387
388 action_map = {'user_deleted_repo':(_('[deleted] repository'), None),
388 action_map = {'user_deleted_repo':(_('[deleted] repository'), None),
389 'user_created_repo':(_('[created] repository'), None),
389 'user_created_repo':(_('[created] repository'), None),
390 'user_forked_repo':(_('[forked] repository'), get_fork_name),
390 'user_forked_repo':(_('[forked] repository'), get_fork_name),
391 'user_updated_repo':(_('[updated] repository'), None),
391 'user_updated_repo':(_('[updated] repository'), None),
392 'admin_deleted_repo':(_('[delete] repository'), None),
392 'admin_deleted_repo':(_('[delete] repository'), None),
393 'admin_created_repo':(_('[created] repository'), None),
393 'admin_created_repo':(_('[created] repository'), None),
394 'admin_forked_repo':(_('[forked] repository'), None),
394 'admin_forked_repo':(_('[forked] repository'), None),
395 'admin_updated_repo':(_('[updated] repository'), None),
395 'admin_updated_repo':(_('[updated] repository'), None),
396 'push':(_('[pushed] into'), get_cs_links),
396 'push':(_('[pushed] into'), get_cs_links),
397 'push_local':(_('[committed via RhodeCode] into'), get_cs_links),
397 'push_local':(_('[committed via RhodeCode] into'), get_cs_links),
398 'push_remote':(_('[pulled from remote] into'), get_cs_links),
398 'push_remote':(_('[pulled from remote] into'), get_cs_links),
399 'pull':(_('[pulled] from'), None),
399 'pull':(_('[pulled] from'), None),
400 'started_following_repo':(_('[started following] repository'), None),
400 'started_following_repo':(_('[started following] repository'), None),
401 'stopped_following_repo':(_('[stopped following] repository'), None),
401 'stopped_following_repo':(_('[stopped following] repository'), None),
402 }
402 }
403
403
404 action_str = action_map.get(action, action)
404 action_str = action_map.get(action, action)
405 if feed:
405 if feed:
406 action = action_str[0].replace('[', '').replace(']', '')
406 action = action_str[0].replace('[', '').replace(']', '')
407 else:
407 else:
408 action = action_str[0].replace('[', '<span class="journal_highlight">')\
408 action = action_str[0].replace('[', '<span class="journal_highlight">')\
409 .replace(']', '</span>')
409 .replace(']', '</span>')
410
410
411 action_params_func = lambda :""
411 action_params_func = lambda :""
412
412
413 if callable(action_str[1]):
413 if callable(action_str[1]):
414 action_params_func = action_str[1]
414 action_params_func = action_str[1]
415
415
416 return [literal(action), action_params_func]
416 return [literal(action), action_params_func]
417
417
418 def action_parser_icon(user_log):
418 def action_parser_icon(user_log):
419 action = user_log.action
419 action = user_log.action
420 action_params = None
420 action_params = None
421 x = action.split(':')
421 x = action.split(':')
422
422
423 if len(x) > 1:
423 if len(x) > 1:
424 action, action_params = x
424 action, action_params = x
425
425
426 tmpl = """<img src="%s%s" alt="%s"/>"""
426 tmpl = """<img src="%s%s" alt="%s"/>"""
427 map = {'user_deleted_repo':'database_delete.png',
427 map = {'user_deleted_repo':'database_delete.png',
428 'user_created_repo':'database_add.png',
428 'user_created_repo':'database_add.png',
429 'user_forked_repo':'arrow_divide.png',
429 'user_forked_repo':'arrow_divide.png',
430 'user_updated_repo':'database_edit.png',
430 'user_updated_repo':'database_edit.png',
431 'admin_deleted_repo':'database_delete.png',
431 'admin_deleted_repo':'database_delete.png',
432 'admin_created_repo':'database_add.png',
432 'admin_created_repo':'database_add.png',
433 'admin_forked_repo':'arrow_divide.png',
433 'admin_forked_repo':'arrow_divide.png',
434 'admin_updated_repo':'database_edit.png',
434 'admin_updated_repo':'database_edit.png',
435 'push':'script_add.png',
435 'push':'script_add.png',
436 'push_local':'script_edit.png',
436 'push_local':'script_edit.png',
437 'push_remote':'connect.png',
437 'push_remote':'connect.png',
438 'pull':'down_16.png',
438 'pull':'down_16.png',
439 'started_following_repo':'heart_add.png',
439 'started_following_repo':'heart_add.png',
440 'stopped_following_repo':'heart_delete.png',
440 'stopped_following_repo':'heart_delete.png',
441 }
441 }
442 return literal(tmpl % ((url('/images/icons/')),
442 return literal(tmpl % ((url('/images/icons/')),
443 map.get(action, action), action))
443 map.get(action, action), action))
444
444
445
445
446 #==============================================================================
446 #==============================================================================
447 # PERMS
447 # PERMS
448 #==============================================================================
448 #==============================================================================
449 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
449 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
450 HasRepoPermissionAny, HasRepoPermissionAll
450 HasRepoPermissionAny, HasRepoPermissionAll
451
451
452 #==============================================================================
452 #==============================================================================
453 # GRAVATAR URL
453 # GRAVATAR URL
454 #==============================================================================
454 #==============================================================================
455
455
456 def gravatar_url(email_address, size=30):
456 def gravatar_url(email_address, size=30):
457 if not str2bool(config['app_conf'].get('use_gravatar')) or \
457 if not str2bool(config['app_conf'].get('use_gravatar')) or \
458 email_address == 'anonymous@rhodecode.org':
458 email_address == 'anonymous@rhodecode.org':
459 return url("/images/user%s.png" % size)
459 return url("/images/user%s.png" % size)
460
460
461 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
461 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
462 default = 'identicon'
462 default = 'identicon'
463 baseurl_nossl = "http://www.gravatar.com/avatar/"
463 baseurl_nossl = "http://www.gravatar.com/avatar/"
464 baseurl_ssl = "https://secure.gravatar.com/avatar/"
464 baseurl_ssl = "https://secure.gravatar.com/avatar/"
465 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
465 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
466
466
467 if isinstance(email_address, unicode):
467 if isinstance(email_address, unicode):
468 #hashlib crashes on unicode items
468 #hashlib crashes on unicode items
469 email_address = safe_str(email_address)
469 email_address = safe_str(email_address)
470 # construct the url
470 # construct the url
471 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
471 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
472 gravatar_url += urllib.urlencode({'d':default, 's':str(size)})
472 gravatar_url += urllib.urlencode({'d':default, 's':str(size)})
473
473
474 return gravatar_url
474 return gravatar_url
475
475
476
476
477 #==============================================================================
477 #==============================================================================
478 # REPO PAGER, PAGER FOR REPOSITORY
478 # REPO PAGER, PAGER FOR REPOSITORY
479 #==============================================================================
479 #==============================================================================
480 class RepoPage(Page):
480 class RepoPage(Page):
481
481
482 def __init__(self, collection, page=1, items_per_page=20,
482 def __init__(self, collection, page=1, items_per_page=20,
483 item_count=None, url=None, branch_name=None, **kwargs):
483 item_count=None, url=None, branch_name=None, **kwargs):
484
484
485 """Create a "RepoPage" instance. special pager for paging
485 """Create a "RepoPage" instance. special pager for paging
486 repository
486 repository
487 """
487 """
488 self._url_generator = url
488 self._url_generator = url
489
489
490 # Safe the kwargs class-wide so they can be used in the pager() method
490 # Safe the kwargs class-wide so they can be used in the pager() method
491 self.kwargs = kwargs
491 self.kwargs = kwargs
492
492
493 # Save a reference to the collection
493 # Save a reference to the collection
494 self.original_collection = collection
494 self.original_collection = collection
495
495
496 self.collection = collection
496 self.collection = collection
497
497
498 # The self.page is the number of the current page.
498 # The self.page is the number of the current page.
499 # The first page has the number 1!
499 # The first page has the number 1!
500 try:
500 try:
501 self.page = int(page) # make it int() if we get it as a string
501 self.page = int(page) # make it int() if we get it as a string
502 except (ValueError, TypeError):
502 except (ValueError, TypeError):
503 self.page = 1
503 self.page = 1
504
504
505 self.items_per_page = items_per_page
505 self.items_per_page = items_per_page
506
506
507 # Unless the user tells us how many items the collections has
507 # Unless the user tells us how many items the collections has
508 # we calculate that ourselves.
508 # we calculate that ourselves.
509 if item_count is not None:
509 if item_count is not None:
510 self.item_count = item_count
510 self.item_count = item_count
511 else:
511 else:
512 self.item_count = len(self.collection)
512 self.item_count = len(self.collection)
513
513
514 # Compute the number of the first and last available page
514 # Compute the number of the first and last available page
515 if self.item_count > 0:
515 if self.item_count > 0:
516 self.first_page = 1
516 self.first_page = 1
517 self.page_count = int(math.ceil(float(self.item_count) /
517 self.page_count = int(math.ceil(float(self.item_count) /
518 self.items_per_page))
518 self.items_per_page))
519 self.last_page = self.first_page + self.page_count - 1
519 self.last_page = self.first_page + self.page_count - 1
520
520
521 # Make sure that the requested page number is the range of valid pages
521 # Make sure that the requested page number is the range of valid pages
522 if self.page > self.last_page:
522 if self.page > self.last_page:
523 self.page = self.last_page
523 self.page = self.last_page
524 elif self.page < self.first_page:
524 elif self.page < self.first_page:
525 self.page = self.first_page
525 self.page = self.first_page
526
526
527 # Note: the number of items on this page can be less than
527 # Note: the number of items on this page can be less than
528 # items_per_page if the last page is not full
528 # items_per_page if the last page is not full
529 self.first_item = max(0, (self.item_count) - (self.page *
529 self.first_item = max(0, (self.item_count) - (self.page *
530 items_per_page))
530 items_per_page))
531 self.last_item = ((self.item_count - 1) - items_per_page *
531 self.last_item = ((self.item_count - 1) - items_per_page *
532 (self.page - 1))
532 (self.page - 1))
533
533
534 iterator = self.collection.get_changesets(start=self.first_item,
534 iterator = self.collection.get_changesets(start=self.first_item,
535 end=self.last_item,
535 end=self.last_item,
536 reverse=True,
536 reverse=True,
537 branch_name=branch_name)
537 branch_name=branch_name)
538 self.items = list(iterator)
538 self.items = list(iterator)
539
539
540 # Links to previous and next page
540 # Links to previous and next page
541 if self.page > self.first_page:
541 if self.page > self.first_page:
542 self.previous_page = self.page - 1
542 self.previous_page = self.page - 1
543 else:
543 else:
544 self.previous_page = None
544 self.previous_page = None
545
545
546 if self.page < self.last_page:
546 if self.page < self.last_page:
547 self.next_page = self.page + 1
547 self.next_page = self.page + 1
548 else:
548 else:
549 self.next_page = None
549 self.next_page = None
550
550
551 # No items available
551 # No items available
552 else:
552 else:
553 self.first_page = None
553 self.first_page = None
554 self.page_count = 0
554 self.page_count = 0
555 self.last_page = None
555 self.last_page = None
556 self.first_item = None
556 self.first_item = None
557 self.last_item = None
557 self.last_item = None
558 self.previous_page = None
558 self.previous_page = None
559 self.next_page = None
559 self.next_page = None
560 self.items = []
560 self.items = []
561
561
562 # This is a subclass of the 'list' type. Initialise the list now.
562 # This is a subclass of the 'list' type. Initialise the list now.
563 list.__init__(self, self.items)
563 list.__init__(self, self.items)
564
564
565
565
566 def changed_tooltip(nodes):
566 def changed_tooltip(nodes):
567 """
567 """
568 Generates a html string for changed nodes in changeset page.
568 Generates a html string for changed nodes in changeset page.
569 It limits the output to 30 entries
569 It limits the output to 30 entries
570
570
571 :param nodes: LazyNodesGenerator
571 :param nodes: LazyNodesGenerator
572 """
572 """
573 if nodes:
573 if nodes:
574 pref = ': <br/> '
574 pref = ': <br/> '
575 suf = ''
575 suf = ''
576 if len(nodes) > 30:
576 if len(nodes) > 30:
577 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
577 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
578 return literal(pref + '<br/> '.join([safe_unicode(x.path)
578 return literal(pref + '<br/> '.join([safe_unicode(x.path)
579 for x in nodes[:30]]) + suf)
579 for x in nodes[:30]]) + suf)
580 else:
580 else:
581 return ': ' + _('No Files')
581 return ': ' + _('No Files')
582
582
583
583
584
584
585 def repo_link(groups_and_repos):
585 def repo_link(groups_and_repos):
586 """
586 """
587 Makes a breadcrumbs link to repo within a group
587 Makes a breadcrumbs link to repo within a group
588 joins &raquo; on each group to create a fancy link
588 joins &raquo; on each group to create a fancy link
589
589
590 ex::
590 ex::
591 group >> subgroup >> repo
591 group >> subgroup >> repo
592
592
593 :param groups_and_repos:
593 :param groups_and_repos:
594 """
594 """
595 groups, repo_name = groups_and_repos
595 groups, repo_name = groups_and_repos
596
596
597 if not groups:
597 if not groups:
598 return repo_name
598 return repo_name
599 else:
599 else:
600 def make_link(group):
600 def make_link(group):
601 return link_to(group.group_name, url('repos_group',
601 return link_to(group.name, url('repos_group_home',
602 id=group.group_id))
602 group_name=group.group_name))
603 return literal(' &raquo; '.join(map(make_link, groups)) + \
603 return literal(' &raquo; '.join(map(make_link, groups)) + \
604 " &raquo; " + repo_name)
604 " &raquo; " + repo_name)
605
605
606
607 def fancy_file_stats(stats):
606 def fancy_file_stats(stats):
608 """
607 """
609 Displays a fancy two colored bar for number of added/deleted
608 Displays a fancy two colored bar for number of added/deleted
610 lines of code on file
609 lines of code on file
611
610
612 :param stats: two element list of added/deleted lines of code
611 :param stats: two element list of added/deleted lines of code
613 """
612 """
614
613
615 a, d, t = stats[0], stats[1], stats[0] + stats[1]
614 a, d, t = stats[0], stats[1], stats[0] + stats[1]
616 width = 100
615 width = 100
617 unit = float(width) / (t or 1)
616 unit = float(width) / (t or 1)
618
617
619 # needs > 9% of width to be visible or 0 to be hidden
618 # needs > 9% of width to be visible or 0 to be hidden
620 a_p = max(9, unit * a) if a > 0 else 0
619 a_p = max(9, unit * a) if a > 0 else 0
621 d_p = max(9, unit * d) if d > 0 else 0
620 d_p = max(9, unit * d) if d > 0 else 0
622 p_sum = a_p + d_p
621 p_sum = a_p + d_p
623
622
624 if p_sum > width:
623 if p_sum > width:
625 #adjust the percentage to be == 100% since we adjusted to 9
624 #adjust the percentage to be == 100% since we adjusted to 9
626 if a_p > d_p:
625 if a_p > d_p:
627 a_p = a_p - (p_sum - width)
626 a_p = a_p - (p_sum - width)
628 else:
627 else:
629 d_p = d_p - (p_sum - width)
628 d_p = d_p - (p_sum - width)
630
629
631 a_v = a if a > 0 else ''
630 a_v = a if a > 0 else ''
632 d_v = d if d > 0 else ''
631 d_v = d if d > 0 else ''
633
632
634
633
635 def cgen(l_type):
634 def cgen(l_type):
636 mapping = {'tr':'top-right-rounded-corner',
635 mapping = {'tr':'top-right-rounded-corner',
637 'tl':'top-left-rounded-corner',
636 'tl':'top-left-rounded-corner',
638 'br':'bottom-right-rounded-corner',
637 'br':'bottom-right-rounded-corner',
639 'bl':'bottom-left-rounded-corner'}
638 'bl':'bottom-left-rounded-corner'}
640 map_getter = lambda x:mapping[x]
639 map_getter = lambda x:mapping[x]
641
640
642 if l_type == 'a' and d_v:
641 if l_type == 'a' and d_v:
643 #case when added and deleted are present
642 #case when added and deleted are present
644 return ' '.join(map(map_getter, ['tl', 'bl']))
643 return ' '.join(map(map_getter, ['tl', 'bl']))
645
644
646 if l_type == 'a' and not d_v:
645 if l_type == 'a' and not d_v:
647 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
646 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
648
647
649 if l_type == 'd' and a_v:
648 if l_type == 'd' and a_v:
650 return ' '.join(map(map_getter, ['tr', 'br']))
649 return ' '.join(map(map_getter, ['tr', 'br']))
651
650
652 if l_type == 'd' and not a_v:
651 if l_type == 'd' and not a_v:
653 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
652 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
654
653
655
654
656
655
657 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (cgen('a'),
656 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (cgen('a'),
658 a_p, a_v)
657 a_p, a_v)
659 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (cgen('d'),
658 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (cgen('d'),
660 d_p, d_v)
659 d_p, d_v)
661 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
660 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
662
661
663
662
664 def urlify_text(text):
663 def urlify_text(text):
665 import re
664 import re
666
665
667 url_pat = re.compile(r'(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)')
666 url_pat = re.compile(r'(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)')
668
667
669 def url_func(match_obj):
668 def url_func(match_obj):
670 url_full = match_obj.groups()[0]
669 url_full = match_obj.groups()[0]
671 return '<a href="%(url)s">%(url)s</a>' % ({'url':url_full})
670 return '<a href="%(url)s">%(url)s</a>' % ({'url':url_full})
672
671
673 return literal(url_pat.sub(url_func, text))
672 return literal(url_pat.sub(url_func, text))
@@ -1,226 +1,226
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.indexers.__init__
3 rhodecode.lib.indexers.__init__
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Whoosh indexing module for RhodeCode
6 Whoosh indexing module for RhodeCode
7
7
8 :created_on: Aug 17, 2010
8 :created_on: Aug 17, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 import os
25 import os
26 import sys
26 import sys
27 import traceback
27 import traceback
28 from os.path import dirname as dn, join as jn
28 from os.path import dirname as dn, join as jn
29
29
30 #to get the rhodecode import
30 #to get the rhodecode import
31 sys.path.append(dn(dn(dn(os.path.realpath(__file__)))))
31 sys.path.append(dn(dn(dn(os.path.realpath(__file__)))))
32
32
33 from string import strip
33 from string import strip
34 from shutil import rmtree
34 from shutil import rmtree
35
35
36 from whoosh.analysis import RegexTokenizer, LowercaseFilter, StopFilter
36 from whoosh.analysis import RegexTokenizer, LowercaseFilter, StopFilter
37 from whoosh.fields import TEXT, ID, STORED, Schema, FieldType
37 from whoosh.fields import TEXT, ID, STORED, Schema, FieldType
38 from whoosh.index import create_in, open_dir
38 from whoosh.index import create_in, open_dir
39 from whoosh.formats import Characters
39 from whoosh.formats import Characters
40 from whoosh.highlight import highlight, SimpleFragmenter, HtmlFormatter
40 from whoosh.highlight import highlight, SimpleFragmenter, HtmlFormatter
41
41
42 from webhelpers.html.builder import escape
42 from webhelpers.html.builder import escape
43 from sqlalchemy import engine_from_config
43 from sqlalchemy import engine_from_config
44 from vcs.utils.lazy import LazyProperty
44 from vcs.utils.lazy import LazyProperty
45
45
46 from rhodecode.model import init_model
46 from rhodecode.model import init_model
47 from rhodecode.model.scm import ScmModel
47 from rhodecode.model.scm import ScmModel
48 from rhodecode.model.repo import RepoModel
48 from rhodecode.model.repo import RepoModel
49 from rhodecode.config.environment import load_environment
49 from rhodecode.config.environment import load_environment
50 from rhodecode.lib import LANGUAGES_EXTENSIONS_MAP
50 from rhodecode.lib import LANGUAGES_EXTENSIONS_MAP
51 from rhodecode.lib.utils import BasePasterCommand, Command, add_cache
51 from rhodecode.lib.utils import BasePasterCommand, Command, add_cache
52
52
53 #EXTENSIONS WE WANT TO INDEX CONTENT OFF
53 #EXTENSIONS WE WANT TO INDEX CONTENT OFF
54 INDEX_EXTENSIONS = LANGUAGES_EXTENSIONS_MAP.keys()
54 INDEX_EXTENSIONS = LANGUAGES_EXTENSIONS_MAP.keys()
55
55
56 #CUSTOM ANALYZER wordsplit + lowercase filter
56 #CUSTOM ANALYZER wordsplit + lowercase filter
57 ANALYZER = RegexTokenizer(expression=r"\w+") | LowercaseFilter()
57 ANALYZER = RegexTokenizer(expression=r"\w+") | LowercaseFilter()
58
58
59
59
60 #INDEX SCHEMA DEFINITION
60 #INDEX SCHEMA DEFINITION
61 SCHEMA = Schema(owner=TEXT(),
61 SCHEMA = Schema(owner=TEXT(),
62 repository=TEXT(stored=True),
62 repository=TEXT(stored=True),
63 path=TEXT(stored=True),
63 path=TEXT(stored=True),
64 content=FieldType(format=Characters(ANALYZER),
64 content=FieldType(format=Characters(ANALYZER),
65 scorable=True, stored=True),
65 scorable=True, stored=True),
66 modtime=STORED(), extension=TEXT(stored=True))
66 modtime=STORED(), extension=TEXT(stored=True))
67
67
68
68
69 IDX_NAME = 'HG_INDEX'
69 IDX_NAME = 'HG_INDEX'
70 FORMATTER = HtmlFormatter('span', between='\n<span class="break">...</span>\n')
70 FORMATTER = HtmlFormatter('span', between='\n<span class="break">...</span>\n')
71 FRAGMENTER = SimpleFragmenter(200)
71 FRAGMENTER = SimpleFragmenter(200)
72
72
73
73
74 class MakeIndex(BasePasterCommand):
74 class MakeIndex(BasePasterCommand):
75
75
76 max_args = 1
76 max_args = 1
77 min_args = 1
77 min_args = 1
78
78
79 usage = "CONFIG_FILE"
79 usage = "CONFIG_FILE"
80 summary = "Creates index for full text search given configuration file"
80 summary = "Creates index for full text search given configuration file"
81 group_name = "RhodeCode"
81 group_name = "RhodeCode"
82 takes_config_file = -1
82 takes_config_file = -1
83 parser = Command.standard_parser(verbose=True)
83 parser = Command.standard_parser(verbose=True)
84
84
85 def command(self):
85 def command(self):
86
86
87 from pylons import config
87 from pylons import config
88 add_cache(config)
88 add_cache(config)
89 engine = engine_from_config(config, 'sqlalchemy.db1.')
89 engine = engine_from_config(config, 'sqlalchemy.db1.')
90 init_model(engine)
90 init_model(engine)
91
91
92 index_location = config['index_dir']
92 index_location = config['index_dir']
93 repo_location = self.options.repo_location \
93 repo_location = self.options.repo_location \
94 if self.options.repo_location else RepoModel().repos_path
94 if self.options.repo_location else RepoModel().repos_path
95 repo_list = map(strip, self.options.repo_list.split(',')) \
95 repo_list = map(strip, self.options.repo_list.split(',')) \
96 if self.options.repo_list else None
96 if self.options.repo_list else None
97
97
98 #======================================================================
98 #======================================================================
99 # WHOOSH DAEMON
99 # WHOOSH DAEMON
100 #======================================================================
100 #======================================================================
101 from rhodecode.lib.pidlock import LockHeld, DaemonLock
101 from rhodecode.lib.pidlock import LockHeld, DaemonLock
102 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
102 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
103 try:
103 try:
104 l = DaemonLock(file=jn(dn(dn(index_location)), 'make_index.lock'))
104 l = DaemonLock(file_=jn(dn(dn(index_location)), 'make_index.lock'))
105 WhooshIndexingDaemon(index_location=index_location,
105 WhooshIndexingDaemon(index_location=index_location,
106 repo_location=repo_location,
106 repo_location=repo_location,
107 repo_list=repo_list)\
107 repo_list=repo_list)\
108 .run(full_index=self.options.full_index)
108 .run(full_index=self.options.full_index)
109 l.release()
109 l.release()
110 except LockHeld:
110 except LockHeld:
111 sys.exit(1)
111 sys.exit(1)
112
112
113 def update_parser(self):
113 def update_parser(self):
114 self.parser.add_option('--repo-location',
114 self.parser.add_option('--repo-location',
115 action='store',
115 action='store',
116 dest='repo_location',
116 dest='repo_location',
117 help="Specifies repositories location to index OPTIONAL",
117 help="Specifies repositories location to index OPTIONAL",
118 )
118 )
119 self.parser.add_option('--index-only',
119 self.parser.add_option('--index-only',
120 action='store',
120 action='store',
121 dest='repo_list',
121 dest='repo_list',
122 help="Specifies a comma separated list of repositores "
122 help="Specifies a comma separated list of repositores "
123 "to build index on OPTIONAL",
123 "to build index on OPTIONAL",
124 )
124 )
125 self.parser.add_option('-f',
125 self.parser.add_option('-f',
126 action='store_true',
126 action='store_true',
127 dest='full_index',
127 dest='full_index',
128 help="Specifies that index should be made full i.e"
128 help="Specifies that index should be made full i.e"
129 " destroy old and build from scratch",
129 " destroy old and build from scratch",
130 default=False)
130 default=False)
131
131
132 class ResultWrapper(object):
132 class ResultWrapper(object):
133 def __init__(self, search_type, searcher, matcher, highlight_items):
133 def __init__(self, search_type, searcher, matcher, highlight_items):
134 self.search_type = search_type
134 self.search_type = search_type
135 self.searcher = searcher
135 self.searcher = searcher
136 self.matcher = matcher
136 self.matcher = matcher
137 self.highlight_items = highlight_items
137 self.highlight_items = highlight_items
138 self.fragment_size = 200 / 2
138 self.fragment_size = 200 / 2
139
139
140 @LazyProperty
140 @LazyProperty
141 def doc_ids(self):
141 def doc_ids(self):
142 docs_id = []
142 docs_id = []
143 while self.matcher.is_active():
143 while self.matcher.is_active():
144 docnum = self.matcher.id()
144 docnum = self.matcher.id()
145 chunks = [offsets for offsets in self.get_chunks()]
145 chunks = [offsets for offsets in self.get_chunks()]
146 docs_id.append([docnum, chunks])
146 docs_id.append([docnum, chunks])
147 self.matcher.next()
147 self.matcher.next()
148 return docs_id
148 return docs_id
149
149
150 def __str__(self):
150 def __str__(self):
151 return '<%s at %s>' % (self.__class__.__name__, len(self.doc_ids))
151 return '<%s at %s>' % (self.__class__.__name__, len(self.doc_ids))
152
152
153 def __repr__(self):
153 def __repr__(self):
154 return self.__str__()
154 return self.__str__()
155
155
156 def __len__(self):
156 def __len__(self):
157 return len(self.doc_ids)
157 return len(self.doc_ids)
158
158
159 def __iter__(self):
159 def __iter__(self):
160 """
160 """
161 Allows Iteration over results,and lazy generate content
161 Allows Iteration over results,and lazy generate content
162
162
163 *Requires* implementation of ``__getitem__`` method.
163 *Requires* implementation of ``__getitem__`` method.
164 """
164 """
165 for docid in self.doc_ids:
165 for docid in self.doc_ids:
166 yield self.get_full_content(docid)
166 yield self.get_full_content(docid)
167
167
168 def __getitem__(self, key):
168 def __getitem__(self, key):
169 """
169 """
170 Slicing of resultWrapper
170 Slicing of resultWrapper
171 """
171 """
172 i, j = key.start, key.stop
172 i, j = key.start, key.stop
173
173
174 slice = []
174 slice = []
175 for docid in self.doc_ids[i:j]:
175 for docid in self.doc_ids[i:j]:
176 slice.append(self.get_full_content(docid))
176 slice.append(self.get_full_content(docid))
177 return slice
177 return slice
178
178
179
179
180 def get_full_content(self, docid):
180 def get_full_content(self, docid):
181 res = self.searcher.stored_fields(docid[0])
181 res = self.searcher.stored_fields(docid[0])
182 f_path = res['path'][res['path'].find(res['repository']) \
182 f_path = res['path'][res['path'].find(res['repository']) \
183 + len(res['repository']):].lstrip('/')
183 + len(res['repository']):].lstrip('/')
184
184
185 content_short = self.get_short_content(res, docid[1])
185 content_short = self.get_short_content(res, docid[1])
186 res.update({'content_short':content_short,
186 res.update({'content_short':content_short,
187 'content_short_hl':self.highlight(content_short),
187 'content_short_hl':self.highlight(content_short),
188 'f_path':f_path})
188 'f_path':f_path})
189
189
190 return res
190 return res
191
191
192 def get_short_content(self, res, chunks):
192 def get_short_content(self, res, chunks):
193
193
194 return ''.join([res['content'][chunk[0]:chunk[1]] for chunk in chunks])
194 return ''.join([res['content'][chunk[0]:chunk[1]] for chunk in chunks])
195
195
196 def get_chunks(self):
196 def get_chunks(self):
197 """
197 """
198 Smart function that implements chunking the content
198 Smart function that implements chunking the content
199 but not overlap chunks so it doesn't highlight the same
199 but not overlap chunks so it doesn't highlight the same
200 close occurrences twice.
200 close occurrences twice.
201
201
202 :param matcher:
202 :param matcher:
203 :param size:
203 :param size:
204 """
204 """
205 memory = [(0, 0)]
205 memory = [(0, 0)]
206 for span in self.matcher.spans():
206 for span in self.matcher.spans():
207 start = span.startchar or 0
207 start = span.startchar or 0
208 end = span.endchar or 0
208 end = span.endchar or 0
209 start_offseted = max(0, start - self.fragment_size)
209 start_offseted = max(0, start - self.fragment_size)
210 end_offseted = end + self.fragment_size
210 end_offseted = end + self.fragment_size
211
211
212 if start_offseted < memory[-1][1]:
212 if start_offseted < memory[-1][1]:
213 start_offseted = memory[-1][1]
213 start_offseted = memory[-1][1]
214 memory.append((start_offseted, end_offseted,))
214 memory.append((start_offseted, end_offseted,))
215 yield (start_offseted, end_offseted,)
215 yield (start_offseted, end_offseted,)
216
216
217 def highlight(self, content, top=5):
217 def highlight(self, content, top=5):
218 if self.search_type != 'content':
218 if self.search_type != 'content':
219 return ''
219 return ''
220 hl = highlight(escape(content),
220 hl = highlight(escape(content),
221 self.highlight_items,
221 self.highlight_items,
222 analyzer=ANALYZER,
222 analyzer=ANALYZER,
223 fragmenter=FRAGMENTER,
223 fragmenter=FRAGMENTER,
224 formatter=FORMATTER,
224 formatter=FORMATTER,
225 top=top)
225 top=top)
226 return hl
226 return hl
@@ -1,289 +1,289
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.middleware.simplegit
3 rhodecode.lib.middleware.simplegit
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 SimpleGit middleware for handling git protocol request (push/clone etc.)
6 SimpleGit middleware for handling git protocol request (push/clone etc.)
7 It's implemented with basic auth function
7 It's implemented with basic auth function
8
8
9 :created_on: Apr 28, 2010
9 :created_on: Apr 28, 2010
10 :author: marcink
10 :author: marcink
11 :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
12 :license: GPLv3, see COPYING for more details.
12 :license: GPLv3, see COPYING for more details.
13 """
13 """
14 # This program is free software: you can redistribute it and/or modify
14 # This program is free software: you can redistribute it and/or modify
15 # it under the terms of the GNU General Public License as published by
15 # it under the terms of the GNU General Public License as published by
16 # the Free Software Foundation, either version 3 of the License, or
16 # the Free Software Foundation, either version 3 of the License, or
17 # (at your option) any later version.
17 # (at your option) any later version.
18 #
18 #
19 # This program is distributed in the hope that it will be useful,
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # GNU General Public License for more details.
22 # GNU General Public License for more details.
23 #
23 #
24 # You should have received a copy of the GNU General Public License
24 # You should have received a copy of the GNU General Public License
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26
26
27 import os
27 import os
28 import logging
28 import logging
29 import traceback
29 import traceback
30
30
31 from dulwich import server as dulserver
31 from dulwich import server as dulserver
32
32
33
33
34 class SimpleGitUploadPackHandler(dulserver.UploadPackHandler):
34 class SimpleGitUploadPackHandler(dulserver.UploadPackHandler):
35
35
36 def handle(self):
36 def handle(self):
37 write = lambda x: self.proto.write_sideband(1, x)
37 write = lambda x: self.proto.write_sideband(1, x)
38
38
39 graph_walker = dulserver.ProtocolGraphWalker(self,
39 graph_walker = dulserver.ProtocolGraphWalker(self,
40 self.repo.object_store,
40 self.repo.object_store,
41 self.repo.get_peeled)
41 self.repo.get_peeled)
42 objects_iter = self.repo.fetch_objects(
42 objects_iter = self.repo.fetch_objects(
43 graph_walker.determine_wants, graph_walker, self.progress,
43 graph_walker.determine_wants, graph_walker, self.progress,
44 get_tagged=self.get_tagged)
44 get_tagged=self.get_tagged)
45
45
46 # Do they want any objects?
46 # Do they want any objects?
47 if objects_iter is None or len(objects_iter) == 0:
47 if objects_iter is None or len(objects_iter) == 0:
48 return
48 return
49
49
50 self.progress("counting objects: %d, done.\n" % len(objects_iter))
50 self.progress("counting objects: %d, done.\n" % len(objects_iter))
51 dulserver.write_pack_objects(dulserver.ProtocolFile(None, write),
51 dulserver.write_pack_objects(dulserver.ProtocolFile(None, write),
52 objects_iter, len(objects_iter))
52 objects_iter, len(objects_iter))
53 messages = []
53 messages = []
54 messages.append('thank you for using rhodecode')
54 messages.append('thank you for using rhodecode')
55
55
56 for msg in messages:
56 for msg in messages:
57 self.progress(msg + "\n")
57 self.progress(msg + "\n")
58 # we are done
58 # we are done
59 self.proto.write("0000")
59 self.proto.write("0000")
60
60
61 dulserver.DEFAULT_HANDLERS = {
61 dulserver.DEFAULT_HANDLERS = {
62 'git-upload-pack': SimpleGitUploadPackHandler,
62 'git-upload-pack': SimpleGitUploadPackHandler,
63 'git-receive-pack': dulserver.ReceivePackHandler,
63 'git-receive-pack': dulserver.ReceivePackHandler,
64 }
64 }
65
65
66 from dulwich.repo import Repo
66 from dulwich.repo import Repo
67 from dulwich.web import HTTPGitApplication
67 from dulwich.web import HTTPGitApplication
68
68
69 from paste.auth.basic import AuthBasicAuthenticator
69 from paste.auth.basic import AuthBasicAuthenticator
70 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
70 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
71
71
72 from rhodecode.lib import safe_str
72 from rhodecode.lib import safe_str
73 from rhodecode.lib.auth import authfunc, HasPermissionAnyMiddleware
73 from rhodecode.lib.auth import authfunc, HasPermissionAnyMiddleware
74 from rhodecode.lib.utils import invalidate_cache, is_valid_repo
74 from rhodecode.lib.utils import invalidate_cache, is_valid_repo
75 from rhodecode.model.db import User
75 from rhodecode.model.db import User
76
76
77 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
77 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
78
78
79 log = logging.getLogger(__name__)
79 log = logging.getLogger(__name__)
80
80
81
81
82 def is_git(environ):
82 def is_git(environ):
83 """Returns True if request's target is git server.
83 """Returns True if request's target is git server.
84 ``HTTP_USER_AGENT`` would then have git client version given.
84 ``HTTP_USER_AGENT`` would then have git client version given.
85
85
86 :param environ:
86 :param environ:
87 """
87 """
88 http_user_agent = environ.get('HTTP_USER_AGENT')
88 http_user_agent = environ.get('HTTP_USER_AGENT')
89 if http_user_agent and http_user_agent.startswith('git'):
89 if http_user_agent and http_user_agent.startswith('git'):
90 return True
90 return True
91 return False
91 return False
92
92
93
93
94 class SimpleGit(object):
94 class SimpleGit(object):
95
95
96 def __init__(self, application, config):
96 def __init__(self, application, config):
97 self.application = application
97 self.application = application
98 self.config = config
98 self.config = config
99 # base path of repo locations
99 # base path of repo locations
100 self.basepath = self.config['base_path']
100 self.basepath = self.config['base_path']
101 #authenticate this mercurial request using authfunc
101 #authenticate this mercurial request using authfunc
102 self.authenticate = AuthBasicAuthenticator('', authfunc)
102 self.authenticate = AuthBasicAuthenticator('', authfunc)
103
103
104 def __call__(self, environ, start_response):
104 def __call__(self, environ, start_response):
105 if not is_git(environ):
105 if not is_git(environ):
106 return self.application(environ, start_response)
106 return self.application(environ, start_response)
107
107
108 proxy_key = 'HTTP_X_REAL_IP'
108 proxy_key = 'HTTP_X_REAL_IP'
109 def_key = 'REMOTE_ADDR'
109 def_key = 'REMOTE_ADDR'
110 ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0'))
110 ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0'))
111 username = None
111 username = None
112 # skip passing error to error controller
112 # skip passing error to error controller
113 environ['pylons.status_code_redirect'] = True
113 environ['pylons.status_code_redirect'] = True
114
114
115 #======================================================================
115 #======================================================================
116 # EXTRACT REPOSITORY NAME FROM ENV
116 # EXTRACT REPOSITORY NAME FROM ENV
117 #======================================================================
117 #======================================================================
118 try:
118 try:
119 repo_name = self.__get_repository(environ)
119 repo_name = self.__get_repository(environ)
120 log.debug('Extracted repo name is %s' % repo_name)
120 log.debug('Extracted repo name is %s' % repo_name)
121 except:
121 except:
122 return HTTPInternalServerError()(environ, start_response)
122 return HTTPInternalServerError()(environ, start_response)
123
123
124 #======================================================================
124 #======================================================================
125 # GET ACTION PULL or PUSH
125 # GET ACTION PULL or PUSH
126 #======================================================================
126 #======================================================================
127 action = self.__get_action(environ)
127 action = self.__get_action(environ)
128
128
129 #======================================================================
129 #======================================================================
130 # CHECK ANONYMOUS PERMISSION
130 # CHECK ANONYMOUS PERMISSION
131 #======================================================================
131 #======================================================================
132 if action in ['pull', 'push']:
132 if action in ['pull', 'push']:
133 anonymous_user = self.__get_user('default')
133 anonymous_user = self.__get_user('default')
134 username = anonymous_user.username
134 username = anonymous_user.username
135 anonymous_perm = self.__check_permission(action,
135 anonymous_perm = self.__check_permission(action,
136 anonymous_user,
136 anonymous_user,
137 repo_name)
137 repo_name)
138
138
139 if anonymous_perm is not True or anonymous_user.active is False:
139 if anonymous_perm is not True or anonymous_user.active is False:
140 if anonymous_perm is not True:
140 if anonymous_perm is not True:
141 log.debug('Not enough credentials to access this '
141 log.debug('Not enough credentials to access this '
142 'repository as anonymous user')
142 'repository as anonymous user')
143 if anonymous_user.active is False:
143 if anonymous_user.active is False:
144 log.debug('Anonymous access is disabled, running '
144 log.debug('Anonymous access is disabled, running '
145 'authentication')
145 'authentication')
146 #==============================================================
146 #==============================================================
147 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
147 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
148 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
148 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
149 #==============================================================
149 #==============================================================
150
150
151 if not REMOTE_USER(environ):
151 if not REMOTE_USER(environ):
152 self.authenticate.realm = \
152 self.authenticate.realm = \
153 safe_str(self.config['rhodecode_realm'])
153 safe_str(self.config['rhodecode_realm'])
154 result = self.authenticate(environ)
154 result = self.authenticate(environ)
155 if isinstance(result, str):
155 if isinstance(result, str):
156 AUTH_TYPE.update(environ, 'basic')
156 AUTH_TYPE.update(environ, 'basic')
157 REMOTE_USER.update(environ, result)
157 REMOTE_USER.update(environ, result)
158 else:
158 else:
159 return result.wsgi_application(environ, start_response)
159 return result.wsgi_application(environ, start_response)
160
160
161 #==============================================================
161 #==============================================================
162 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME FROM
162 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME FROM
163 # BASIC AUTH
163 # BASIC AUTH
164 #==============================================================
164 #==============================================================
165
165
166 if action in ['pull', 'push']:
166 if action in ['pull', 'push']:
167 username = REMOTE_USER(environ)
167 username = REMOTE_USER(environ)
168 try:
168 try:
169 user = self.__get_user(username)
169 user = self.__get_user(username)
170 username = user.username
170 username = user.username
171 except:
171 except:
172 log.error(traceback.format_exc())
172 log.error(traceback.format_exc())
173 return HTTPInternalServerError()(environ,
173 return HTTPInternalServerError()(environ,
174 start_response)
174 start_response)
175
175
176 #check permissions for this repository
176 #check permissions for this repository
177 perm = self.__check_permission(action, user,
177 perm = self.__check_permission(action, user,
178 repo_name)
178 repo_name)
179 if perm is not True:
179 if perm is not True:
180 return HTTPForbidden()(environ, start_response)
180 return HTTPForbidden()(environ, start_response)
181
181
182 extras = {'ip': ipaddr,
182 extras = {'ip': ipaddr,
183 'username': username,
183 'username': username,
184 'action': action,
184 'action': action,
185 'repository': repo_name}
185 'repository': repo_name}
186
186
187 #===================================================================
187 #===================================================================
188 # GIT REQUEST HANDLING
188 # GIT REQUEST HANDLING
189 #===================================================================
189 #===================================================================
190
190
191 repo_path = safe_str(os.path.join(self.basepath, repo_name))
191 repo_path = safe_str(os.path.join(self.basepath, repo_name))
192 log.debug('Repository path is %s' % repo_path)
192 log.debug('Repository path is %s' % repo_path)
193
193
194 # quick check if that dir exists...
194 # quick check if that dir exists...
195 if is_valid_repo(repo_name, self.basepath) is False:
195 if is_valid_repo(repo_name, self.basepath) is False:
196 return HTTPNotFound()(environ, start_response)
196 return HTTPNotFound()(environ, start_response)
197
197
198 try:
198 try:
199 #invalidate cache on push
199 #invalidate cache on push
200 if action == 'push':
200 if action == 'push':
201 self.__invalidate_cache(repo_name)
201 self.__invalidate_cache(repo_name)
202
202
203 app = self.__make_app(repo_name, repo_path)
203 app = self.__make_app(repo_name, repo_path)
204 return app(environ, start_response)
204 return app(environ, start_response)
205 except Exception:
205 except Exception:
206 log.error(traceback.format_exc())
206 log.error(traceback.format_exc())
207 return HTTPInternalServerError()(environ, start_response)
207 return HTTPInternalServerError()(environ, start_response)
208
208
209 def __make_app(self, repo_name, repo_path):
209 def __make_app(self, repo_name, repo_path):
210 """
210 """
211 Make an wsgi application using dulserver
211 Make an wsgi application using dulserver
212
212
213 :param repo_name: name of the repository
213 :param repo_name: name of the repository
214 :param repo_path: full path to the repository
214 :param repo_path: full path to the repository
215 """
215 """
216
216
217 _d = {'/' + repo_name: Repo(repo_path)}
217 _d = {'/' + repo_name: Repo(repo_path)}
218 backend = dulserver.DictBackend(_d)
218 backend = dulserver.DictBackend(_d)
219 gitserve = HTTPGitApplication(backend)
219 gitserve = HTTPGitApplication(backend)
220
220
221 return gitserve
221 return gitserve
222
222
223 def __check_permission(self, action, user, repo_name):
223 def __check_permission(self, action, user, repo_name):
224 """
224 """
225 Checks permissions using action (push/pull) user and repository
225 Checks permissions using action (push/pull) user and repository
226 name
226 name
227
227
228 :param action: push or pull action
228 :param action: push or pull action
229 :param user: user instance
229 :param user: user instance
230 :param repo_name: repository name
230 :param repo_name: repository name
231 """
231 """
232 if action == 'push':
232 if action == 'push':
233 if not HasPermissionAnyMiddleware('repository.write',
233 if not HasPermissionAnyMiddleware('repository.write',
234 'repository.admin')(user,
234 'repository.admin')(user,
235 repo_name):
235 repo_name):
236 return False
236 return False
237
237
238 else:
238 else:
239 #any other action need at least read permission
239 #any other action need at least read permission
240 if not HasPermissionAnyMiddleware('repository.read',
240 if not HasPermissionAnyMiddleware('repository.read',
241 'repository.write',
241 'repository.write',
242 'repository.admin')(user,
242 'repository.admin')(user,
243 repo_name):
243 repo_name):
244 return False
244 return False
245
245
246 return True
246 return True
247
247
248 def __get_repository(self, environ):
248 def __get_repository(self, environ):
249 """
249 """
250 Get's repository name out of PATH_INFO header
250 Get's repository name out of PATH_INFO header
251
251
252 :param environ: environ where PATH_INFO is stored
252 :param environ: environ where PATH_INFO is stored
253 """
253 """
254 try:
254 try:
255 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
255 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
256 if repo_name.endswith('/'):
256 if repo_name.endswith('/'):
257 repo_name = repo_name.rstrip('/')
257 repo_name = repo_name.rstrip('/')
258 except:
258 except:
259 log.error(traceback.format_exc())
259 log.error(traceback.format_exc())
260 raise
260 raise
261 repo_name = repo_name.split('/')[0]
261 repo_name = repo_name.split('/')[0]
262 return repo_name
262 return repo_name
263
263
264 def __get_user(self, username):
264 def __get_user(self, username):
265 return User.by_username(username)
265 return User.get_by_username(username)
266
266
267 def __get_action(self, environ):
267 def __get_action(self, environ):
268 """Maps git request commands into a pull or push command.
268 """Maps git request commands into a pull or push command.
269
269
270 :param environ:
270 :param environ:
271 """
271 """
272 service = environ['QUERY_STRING'].split('=')
272 service = environ['QUERY_STRING'].split('=')
273 if len(service) > 1:
273 if len(service) > 1:
274 service_cmd = service[1]
274 service_cmd = service[1]
275 mapping = {'git-receive-pack': 'push',
275 mapping = {'git-receive-pack': 'push',
276 'git-upload-pack': 'pull',
276 'git-upload-pack': 'pull',
277 }
277 }
278
278
279 return mapping.get(service_cmd,
279 return mapping.get(service_cmd,
280 service_cmd if service_cmd else 'other')
280 service_cmd if service_cmd else 'other')
281 else:
281 else:
282 return 'other'
282 return 'other'
283
283
284 def __invalidate_cache(self, repo_name):
284 def __invalidate_cache(self, repo_name):
285 """we know that some change was made to repositories and we should
285 """we know that some change was made to repositories and we should
286 invalidate the cache to see the changes right away but only for
286 invalidate the cache to see the changes right away but only for
287 push requests"""
287 push requests"""
288 invalidate_cache('get_repo_cached_%s' % repo_name)
288 invalidate_cache('get_repo_cached_%s' % repo_name)
289
289
@@ -1,287 +1,288
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.middleware.simplehg
3 rhodecode.lib.middleware.simplehg
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 SimpleHG middleware for handling mercurial protocol request
6 SimpleHG middleware for handling mercurial protocol request
7 (push/clone etc.). It's implemented with basic auth function
7 (push/clone etc.). It's implemented with basic auth function
8
8
9 :created_on: Apr 28, 2010
9 :created_on: Apr 28, 2010
10 :author: marcink
10 :author: marcink
11 :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2009-2010 Marcin Kuzminski <marcin@python-works.com>
12 :license: GPLv3, see COPYING for more details.
12 :license: GPLv3, see COPYING for more details.
13 """
13 """
14 # This program is free software: you can redistribute it and/or modify
14 # This program is free software: you can redistribute it and/or modify
15 # it under the terms of the GNU General Public License as published by
15 # it under the terms of the GNU General Public License as published by
16 # the Free Software Foundation, either version 3 of the License, or
16 # the Free Software Foundation, either version 3 of the License, or
17 # (at your option) any later version.
17 # (at your option) any later version.
18 #
18 #
19 # This program is distributed in the hope that it will be useful,
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # GNU General Public License for more details.
22 # GNU General Public License for more details.
23 #
23 #
24 # You should have received a copy of the GNU General Public License
24 # You should have received a copy of the GNU General Public License
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26
26
27 import os
27 import os
28 import logging
28 import logging
29 import traceback
29 import traceback
30
30
31 from mercurial.error import RepoError
31 from mercurial.error import RepoError
32 from mercurial.hgweb import hgweb_mod
32 from mercurial.hgweb import hgweb_mod
33
33
34 from paste.auth.basic import AuthBasicAuthenticator
34 from paste.auth.basic import AuthBasicAuthenticator
35 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
35 from paste.httpheaders import REMOTE_USER, AUTH_TYPE
36
36
37 from rhodecode.lib import safe_str
37 from rhodecode.lib import safe_str
38 from rhodecode.lib.auth import authfunc, HasPermissionAnyMiddleware
38 from rhodecode.lib.auth import authfunc, HasPermissionAnyMiddleware
39 from rhodecode.lib.utils import make_ui, invalidate_cache, \
39 from rhodecode.lib.utils import make_ui, invalidate_cache, \
40 is_valid_repo, ui_sections
40 is_valid_repo, ui_sections
41 from rhodecode.model.db import User
41 from rhodecode.model.db import User
42
42
43 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
43 from webob.exc import HTTPNotFound, HTTPForbidden, HTTPInternalServerError
44
44
45 log = logging.getLogger(__name__)
45 log = logging.getLogger(__name__)
46
46
47
47
48 def is_mercurial(environ):
48 def is_mercurial(environ):
49 """Returns True if request's target is mercurial server - header
49 """Returns True if request's target is mercurial server - header
50 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
50 ``HTTP_ACCEPT`` of such request would start with ``application/mercurial``.
51 """
51 """
52 http_accept = environ.get('HTTP_ACCEPT')
52 http_accept = environ.get('HTTP_ACCEPT')
53 if http_accept and http_accept.startswith('application/mercurial'):
53 if http_accept and http_accept.startswith('application/mercurial'):
54 return True
54 return True
55 return False
55 return False
56
56
57
57
58 class SimpleHg(object):
58 class SimpleHg(object):
59
59
60 def __init__(self, application, config):
60 def __init__(self, application, config):
61 self.application = application
61 self.application = application
62 self.config = config
62 self.config = config
63 # base path of repo locations
63 # base path of repo locations
64 self.basepath = self.config['base_path']
64 self.basepath = self.config['base_path']
65 #authenticate this mercurial request using authfunc
65 #authenticate this mercurial request using authfunc
66 self.authenticate = AuthBasicAuthenticator('', authfunc)
66 self.authenticate = AuthBasicAuthenticator('', authfunc)
67 self.ipaddr = '0.0.0.0'
67 self.ipaddr = '0.0.0.0'
68
68
69 def __call__(self, environ, start_response):
69 def __call__(self, environ, start_response):
70 if not is_mercurial(environ):
70 if not is_mercurial(environ):
71 return self.application(environ, start_response)
71 return self.application(environ, start_response)
72
72
73 proxy_key = 'HTTP_X_REAL_IP'
73 proxy_key = 'HTTP_X_REAL_IP'
74 def_key = 'REMOTE_ADDR'
74 def_key = 'REMOTE_ADDR'
75 ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0'))
75 ipaddr = environ.get(proxy_key, environ.get(def_key, '0.0.0.0'))
76
76
77 # skip passing error to error controller
77 # skip passing error to error controller
78 environ['pylons.status_code_redirect'] = True
78 environ['pylons.status_code_redirect'] = True
79
79
80 #======================================================================
80 #======================================================================
81 # EXTRACT REPOSITORY NAME FROM ENV
81 # EXTRACT REPOSITORY NAME FROM ENV
82 #======================================================================
82 #======================================================================
83 try:
83 try:
84 repo_name = environ['REPO_NAME'] = self.__get_repository(environ)
84 repo_name = environ['REPO_NAME'] = self.__get_repository(environ)
85 log.debug('Extracted repo name is %s' % repo_name)
85 log.debug('Extracted repo name is %s' % repo_name)
86 except:
86 except:
87 return HTTPInternalServerError()(environ, start_response)
87 return HTTPInternalServerError()(environ, start_response)
88
88
89 #======================================================================
89 #======================================================================
90 # GET ACTION PULL or PUSH
90 # GET ACTION PULL or PUSH
91 #======================================================================
91 #======================================================================
92 action = self.__get_action(environ)
92 action = self.__get_action(environ)
93
93
94 #======================================================================
94 #======================================================================
95 # CHECK ANONYMOUS PERMISSION
95 # CHECK ANONYMOUS PERMISSION
96 #======================================================================
96 #======================================================================
97 if action in ['pull', 'push']:
97 if action in ['pull', 'push']:
98 anonymous_user = self.__get_user('default')
98 anonymous_user = self.__get_user('default')
99
99 username = anonymous_user.username
100 username = anonymous_user.username
100 anonymous_perm = self.__check_permission(action,
101 anonymous_perm = self.__check_permission(action,
101 anonymous_user,
102 anonymous_user,
102 repo_name)
103 repo_name)
103
104
104 if anonymous_perm is not True or anonymous_user.active is False:
105 if anonymous_perm is not True or anonymous_user.active is False:
105 if anonymous_perm is not True:
106 if anonymous_perm is not True:
106 log.debug('Not enough credentials to access this '
107 log.debug('Not enough credentials to access this '
107 'repository as anonymous user')
108 'repository as anonymous user')
108 if anonymous_user.active is False:
109 if anonymous_user.active is False:
109 log.debug('Anonymous access is disabled, running '
110 log.debug('Anonymous access is disabled, running '
110 'authentication')
111 'authentication')
111 #==============================================================
112 #==============================================================
112 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
113 # DEFAULT PERM FAILED OR ANONYMOUS ACCESS IS DISABLED SO WE
113 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
114 # NEED TO AUTHENTICATE AND ASK FOR AUTH USER PERMISSIONS
114 #==============================================================
115 #==============================================================
115
116
116 if not REMOTE_USER(environ):
117 if not REMOTE_USER(environ):
117 self.authenticate.realm = \
118 self.authenticate.realm = \
118 safe_str(self.config['rhodecode_realm'])
119 safe_str(self.config['rhodecode_realm'])
119 result = self.authenticate(environ)
120 result = self.authenticate(environ)
120 if isinstance(result, str):
121 if isinstance(result, str):
121 AUTH_TYPE.update(environ, 'basic')
122 AUTH_TYPE.update(environ, 'basic')
122 REMOTE_USER.update(environ, result)
123 REMOTE_USER.update(environ, result)
123 else:
124 else:
124 return result.wsgi_application(environ, start_response)
125 return result.wsgi_application(environ, start_response)
125
126
126 #==============================================================
127 #==============================================================
127 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME FROM
128 # CHECK PERMISSIONS FOR THIS REQUEST USING GIVEN USERNAME FROM
128 # BASIC AUTH
129 # BASIC AUTH
129 #==============================================================
130 #==============================================================
130
131
131 if action in ['pull', 'push']:
132 if action in ['pull', 'push']:
132 username = REMOTE_USER(environ)
133 username = REMOTE_USER(environ)
133 try:
134 try:
134 user = self.__get_user(username)
135 user = self.__get_user(username)
135 username = user.username
136 username = user.username
136 except:
137 except:
137 log.error(traceback.format_exc())
138 log.error(traceback.format_exc())
138 return HTTPInternalServerError()(environ,
139 return HTTPInternalServerError()(environ,
139 start_response)
140 start_response)
140
141
141 #check permissions for this repository
142 #check permissions for this repository
142 perm = self.__check_permission(action, user,
143 perm = self.__check_permission(action, user,
143 repo_name)
144 repo_name)
144 if perm is not True:
145 if perm is not True:
145 return HTTPForbidden()(environ, start_response)
146 return HTTPForbidden()(environ, start_response)
146
147
147 extras = {'ip': ipaddr,
148 extras = {'ip': ipaddr,
148 'username': username,
149 'username': username,
149 'action': action,
150 'action': action,
150 'repository': repo_name}
151 'repository': repo_name}
151
152
152 #======================================================================
153 #======================================================================
153 # MERCURIAL REQUEST HANDLING
154 # MERCURIAL REQUEST HANDLING
154 #======================================================================
155 #======================================================================
155
156
156 repo_path = safe_str(os.path.join(self.basepath, repo_name))
157 repo_path = safe_str(os.path.join(self.basepath, repo_name))
157 log.debug('Repository path is %s' % repo_path)
158 log.debug('Repository path is %s' % repo_path)
158
159
159 baseui = make_ui('db')
160 baseui = make_ui('db')
160 self.__inject_extras(repo_path, baseui, extras)
161 self.__inject_extras(repo_path, baseui, extras)
161
162
162
163
163 # quick check if that dir exists...
164 # quick check if that dir exists...
164 if is_valid_repo(repo_name, self.basepath) is False:
165 if is_valid_repo(repo_name, self.basepath) is False:
165 return HTTPNotFound()(environ, start_response)
166 return HTTPNotFound()(environ, start_response)
166
167
167 try:
168 try:
168 #invalidate cache on push
169 #invalidate cache on push
169 if action == 'push':
170 if action == 'push':
170 self.__invalidate_cache(repo_name)
171 self.__invalidate_cache(repo_name)
171
172
172 app = self.__make_app(repo_path, baseui, extras)
173 app = self.__make_app(repo_path, baseui, extras)
173 return app(environ, start_response)
174 return app(environ, start_response)
174 except RepoError, e:
175 except RepoError, e:
175 if str(e).find('not found') != -1:
176 if str(e).find('not found') != -1:
176 return HTTPNotFound()(environ, start_response)
177 return HTTPNotFound()(environ, start_response)
177 except Exception:
178 except Exception:
178 log.error(traceback.format_exc())
179 log.error(traceback.format_exc())
179 return HTTPInternalServerError()(environ, start_response)
180 return HTTPInternalServerError()(environ, start_response)
180
181
181 def __make_app(self, repo_name, baseui, extras):
182 def __make_app(self, repo_name, baseui, extras):
182 """
183 """
183 Make an wsgi application using hgweb, and inject generated baseui
184 Make an wsgi application using hgweb, and inject generated baseui
184 instance, additionally inject some extras into ui object
185 instance, additionally inject some extras into ui object
185 """
186 """
186 return hgweb_mod.hgweb(repo_name, name=repo_name, baseui=baseui)
187 return hgweb_mod.hgweb(repo_name, name=repo_name, baseui=baseui)
187
188
188
189
189 def __check_permission(self, action, user, repo_name):
190 def __check_permission(self, action, user, repo_name):
190 """
191 """
191 Checks permissions using action (push/pull) user and repository
192 Checks permissions using action (push/pull) user and repository
192 name
193 name
193
194
194 :param action: push or pull action
195 :param action: push or pull action
195 :param user: user instance
196 :param user: user instance
196 :param repo_name: repository name
197 :param repo_name: repository name
197 """
198 """
198 if action == 'push':
199 if action == 'push':
199 if not HasPermissionAnyMiddleware('repository.write',
200 if not HasPermissionAnyMiddleware('repository.write',
200 'repository.admin')(user,
201 'repository.admin')(user,
201 repo_name):
202 repo_name):
202 return False
203 return False
203
204
204 else:
205 else:
205 #any other action need at least read permission
206 #any other action need at least read permission
206 if not HasPermissionAnyMiddleware('repository.read',
207 if not HasPermissionAnyMiddleware('repository.read',
207 'repository.write',
208 'repository.write',
208 'repository.admin')(user,
209 'repository.admin')(user,
209 repo_name):
210 repo_name):
210 return False
211 return False
211
212
212 return True
213 return True
213
214
214 def __get_repository(self, environ):
215 def __get_repository(self, environ):
215 """
216 """
216 Get's repository name out of PATH_INFO header
217 Get's repository name out of PATH_INFO header
217
218
218 :param environ: environ where PATH_INFO is stored
219 :param environ: environ where PATH_INFO is stored
219 """
220 """
220 try:
221 try:
221 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
222 repo_name = '/'.join(environ['PATH_INFO'].split('/')[1:])
222 if repo_name.endswith('/'):
223 if repo_name.endswith('/'):
223 repo_name = repo_name.rstrip('/')
224 repo_name = repo_name.rstrip('/')
224 except:
225 except:
225 log.error(traceback.format_exc())
226 log.error(traceback.format_exc())
226 raise
227 raise
227
228
228 return repo_name
229 return repo_name
229
230
230 def __get_user(self, username):
231 def __get_user(self, username):
231 return User.by_username(username)
232 return User.get_by_username(username)
232
233
233 def __get_action(self, environ):
234 def __get_action(self, environ):
234 """
235 """
235 Maps mercurial request commands into a clone,pull or push command.
236 Maps mercurial request commands into a clone,pull or push command.
236 This should always return a valid command string
237 This should always return a valid command string
237
238
238 :param environ:
239 :param environ:
239 """
240 """
240 mapping = {'changegroup': 'pull',
241 mapping = {'changegroup': 'pull',
241 'changegroupsubset': 'pull',
242 'changegroupsubset': 'pull',
242 'stream_out': 'pull',
243 'stream_out': 'pull',
243 'listkeys': 'pull',
244 'listkeys': 'pull',
244 'unbundle': 'push',
245 'unbundle': 'push',
245 'pushkey': 'push', }
246 'pushkey': 'push', }
246 for qry in environ['QUERY_STRING'].split('&'):
247 for qry in environ['QUERY_STRING'].split('&'):
247 if qry.startswith('cmd'):
248 if qry.startswith('cmd'):
248 cmd = qry.split('=')[-1]
249 cmd = qry.split('=')[-1]
249 if cmd in mapping:
250 if cmd in mapping:
250 return mapping[cmd]
251 return mapping[cmd]
251 else:
252 else:
252 return 'pull'
253 return 'pull'
253
254
254 def __invalidate_cache(self, repo_name):
255 def __invalidate_cache(self, repo_name):
255 """we know that some change was made to repositories and we should
256 """we know that some change was made to repositories and we should
256 invalidate the cache to see the changes right away but only for
257 invalidate the cache to see the changes right away but only for
257 push requests"""
258 push requests"""
258 invalidate_cache('get_repo_cached_%s' % repo_name)
259 invalidate_cache('get_repo_cached_%s' % repo_name)
259
260
260 def __inject_extras(self,repo_path, baseui, extras={}):
261 def __inject_extras(self, repo_path, baseui, extras={}):
261 """
262 """
262 Injects some extra params into baseui instance
263 Injects some extra params into baseui instance
263
264
264 also overwrites global settings with those takes from local hgrc file
265 also overwrites global settings with those takes from local hgrc file
265
266
266 :param baseui: baseui instance
267 :param baseui: baseui instance
267 :param extras: dict with extra params to put into baseui
268 :param extras: dict with extra params to put into baseui
268 """
269 """
269
270
270 hgrc = os.path.join(repo_path, '.hg', 'hgrc')
271 hgrc = os.path.join(repo_path, '.hg', 'hgrc')
271
272
272 # make our hgweb quiet so it doesn't print output
273 # make our hgweb quiet so it doesn't print output
273 baseui.setconfig('ui', 'quiet', 'true')
274 baseui.setconfig('ui', 'quiet', 'true')
274
275
275 #inject some additional parameters that will be available in ui
276 #inject some additional parameters that will be available in ui
276 #for hooks
277 #for hooks
277 for k, v in extras.items():
278 for k, v in extras.items():
278 baseui.setconfig('rhodecode_extras', k, v)
279 baseui.setconfig('rhodecode_extras', k, v)
279
280
280 repoui = make_ui('file', hgrc, False)
281 repoui = make_ui('file', hgrc, False)
281
282
282 if repoui:
283 if repoui:
283 #overwrite our ui instance with the section from hgrc file
284 #overwrite our ui instance with the section from hgrc file
284 for section in ui_sections:
285 for section in ui_sections:
285 for k, v in repoui.configitems(section):
286 for k, v in repoui.configitems(section):
286 baseui.setconfig(section, k, v)
287 baseui.setconfig(section, k, v)
287
288
@@ -1,142 +1,129
1 import os
1 import os
2 import sys
2 import sys
3 import time
3 import time
4 import errno
4 import errno
5
5
6 from warnings import warn
6 from warnings import warn
7 from multiprocessing.util import Finalize
7 from multiprocessing.util import Finalize
8
8
9 from rhodecode import __platform__, PLATFORM_WIN
9 from rhodecode.lib.compat import kill
10
11 if __platform__ in PLATFORM_WIN:
12 import ctypes
13
14 def kill(pid, sig):
15 """kill function for Win32"""
16 kernel32 = ctypes.windll.kernel32
17 handle = kernel32.OpenProcess(1, 0, pid)
18 return (0 != kernel32.TerminateProcess(handle, 0))
19
20 else:
21 kill = os.kill
22
23
10
24 class LockHeld(Exception):
11 class LockHeld(Exception):
25 pass
12 pass
26
13
27
14
28 class DaemonLock(object):
15 class DaemonLock(object):
29 """daemon locking
16 """daemon locking
30 USAGE:
17 USAGE:
31 try:
18 try:
32 l = DaemonLock(desc='test lock')
19 l = DaemonLock(file_='/path/tolockfile',desc='test lock')
33 main()
20 main()
34 l.release()
21 l.release()
35 except LockHeld:
22 except LockHeld:
36 sys.exit(1)
23 sys.exit(1)
37 """
24 """
38
25
39 def __init__(self, file=None, callbackfn=None,
26 def __init__(self, file_=None, callbackfn=None,
40 desc='daemon lock', debug=False):
27 desc='daemon lock', debug=False):
41
28
42 self.pidfile = file if file else os.path.join(
29 self.pidfile = file_ if file_ else os.path.join(
43 os.path.dirname(__file__),
30 os.path.dirname(__file__),
44 'running.lock')
31 'running.lock')
45 self.callbackfn = callbackfn
32 self.callbackfn = callbackfn
46 self.desc = desc
33 self.desc = desc
47 self.debug = debug
34 self.debug = debug
48 self.held = False
35 self.held = False
49 #run the lock automatically !
36 #run the lock automatically !
50 self.lock()
37 self.lock()
51 self._finalize = Finalize(self, DaemonLock._on_finalize,
38 self._finalize = Finalize(self, DaemonLock._on_finalize,
52 args=(self, debug), exitpriority=10)
39 args=(self, debug), exitpriority=10)
53
40
54 @staticmethod
41 @staticmethod
55 def _on_finalize(lock, debug):
42 def _on_finalize(lock, debug):
56 if lock.held:
43 if lock.held:
57 if debug:
44 if debug:
58 print 'leck held finilazing and running lock.release()'
45 print 'leck held finilazing and running lock.release()'
59 lock.release()
46 lock.release()
60
47
61 def lock(self):
48 def lock(self):
62 """
49 """
63 locking function, if lock is present it
50 locking function, if lock is present it
64 will raise LockHeld exception
51 will raise LockHeld exception
65 """
52 """
66 lockname = '%s' % (os.getpid())
53 lockname = '%s' % (os.getpid())
67 if self.debug:
54 if self.debug:
68 print 'running lock'
55 print 'running lock'
69 self.trylock()
56 self.trylock()
70 self.makelock(lockname, self.pidfile)
57 self.makelock(lockname, self.pidfile)
71 return True
58 return True
72
59
73 def trylock(self):
60 def trylock(self):
74 running_pid = False
61 running_pid = False
75 if self.debug:
62 if self.debug:
76 print 'checking for already running process'
63 print 'checking for already running process'
77 try:
64 try:
78 pidfile = open(self.pidfile, "r")
65 pidfile = open(self.pidfile, "r")
79 pidfile.seek(0)
66 pidfile.seek(0)
80 running_pid = int(pidfile.readline())
67 running_pid = int(pidfile.readline())
81
68
82 pidfile.close()
69 pidfile.close()
83
70
84 if self.debug:
71 if self.debug:
85 print ('lock file present running_pid: %s, '
72 print ('lock file present running_pid: %s, '
86 'checking for execution') % running_pid
73 'checking for execution') % running_pid
87 # Now we check the PID from lock file matches to the current
74 # Now we check the PID from lock file matches to the current
88 # process PID
75 # process PID
89 if running_pid:
76 if running_pid:
90 try:
77 try:
91 kill(running_pid, 0)
78 kill(running_pid, 0)
92 except OSError, exc:
79 except OSError, exc:
93 if exc.errno in (errno.ESRCH, errno.EPERM):
80 if exc.errno in (errno.ESRCH, errno.EPERM):
94 print ("Lock File is there but"
81 print ("Lock File is there but"
95 " the program is not running")
82 " the program is not running")
96 print "Removing lock file for the: %s" % running_pid
83 print "Removing lock file for the: %s" % running_pid
97 self.release()
84 self.release()
98 else:
85 else:
99 raise
86 raise
100 else:
87 else:
101 print "You already have an instance of the program running"
88 print "You already have an instance of the program running"
102 print "It is running as process %s" % running_pid
89 print "It is running as process %s" % running_pid
103 raise LockHeld()
90 raise LockHeld()
104
91
105 except IOError, e:
92 except IOError, e:
106 if e.errno != 2:
93 if e.errno != 2:
107 raise
94 raise
108
95
109 def release(self):
96 def release(self):
110 """releases the pid by removing the pidfile
97 """releases the pid by removing the pidfile
111 """
98 """
112 if self.debug:
99 if self.debug:
113 print 'trying to release the pidlock'
100 print 'trying to release the pidlock'
114
101
115 if self.callbackfn:
102 if self.callbackfn:
116 #execute callback function on release
103 #execute callback function on release
117 if self.debug:
104 if self.debug:
118 print 'executing callback function %s' % self.callbackfn
105 print 'executing callback function %s' % self.callbackfn
119 self.callbackfn()
106 self.callbackfn()
120 try:
107 try:
121 if self.debug:
108 if self.debug:
122 print 'removing pidfile %s' % self.pidfile
109 print 'removing pidfile %s' % self.pidfile
123 os.remove(self.pidfile)
110 os.remove(self.pidfile)
124 self.held = False
111 self.held = False
125 except OSError, e:
112 except OSError, e:
126 if self.debug:
113 if self.debug:
127 print 'removing pidfile failed %s' % e
114 print 'removing pidfile failed %s' % e
128 pass
115 pass
129
116
130 def makelock(self, lockname, pidfile):
117 def makelock(self, lockname, pidfile):
131 """
118 """
132 this function will make an actual lock
119 this function will make an actual lock
133
120
134 :param lockname: acctual pid of file
121 :param lockname: acctual pid of file
135 :param pidfile: the file to write the pid in
122 :param pidfile: the file to write the pid in
136 """
123 """
137 if self.debug:
124 if self.debug:
138 print 'creating a file %s and pid: %s' % (pidfile, lockname)
125 print 'creating a file %s and pid: %s' % (pidfile, lockname)
139 pidfile = open(self.pidfile, "wb")
126 pidfile = open(self.pidfile, "wb")
140 pidfile.write(lockname)
127 pidfile.write(lockname)
141 pidfile.close
128 pidfile.close
142 self.held = True
129 self.held = True
@@ -1,149 +1,161
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.smtp_mailer
3 rhodecode.lib.smtp_mailer
4 ~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Simple smtp mailer used in RhodeCode
6 Simple smtp mailer used in RhodeCode
7
7
8 :created_on: Sep 13, 2010
8 :created_on: Sep 13, 2010
9 :copyright: (c) 2011 by marcink.
9 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :license: LICENSE_NAME, see LICENSE_FILE for more details.
10 :license: GPLv3, see COPYING for more details.
11 """
11 """
12 # This program is free software: you can redistribute it and/or modify
13 # it under the terms of the GNU General Public License as published by
14 # the Free Software Foundation, either version 3 of the License, or
15 # (at your option) any later version.
16 #
17 # This program is distributed in the hope that it will be useful,
18 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # GNU General Public License for more details.
21 #
22 # You should have received a copy of the GNU General Public License
23 # along with this program. If not, see <http://www.gnu.org/licenses/>.
12
24
13 import logging
25 import logging
14 import smtplib
26 import smtplib
15 import mimetypes
27 import mimetypes
16 from socket import sslerror
28 from socket import sslerror
17
29
18 from email.mime.multipart import MIMEMultipart
30 from email.mime.multipart import MIMEMultipart
19 from email.mime.image import MIMEImage
31 from email.mime.image import MIMEImage
20 from email.mime.audio import MIMEAudio
32 from email.mime.audio import MIMEAudio
21 from email.mime.base import MIMEBase
33 from email.mime.base import MIMEBase
22 from email.mime.text import MIMEText
34 from email.mime.text import MIMEText
23 from email.utils import formatdate
35 from email.utils import formatdate
24 from email import encoders
36 from email import encoders
25
37
26
38
27 class SmtpMailer(object):
39 class SmtpMailer(object):
28 """SMTP mailer class
40 """SMTP mailer class
29
41
30 mailer = SmtpMailer(mail_from, user, passwd, mail_server,
42 mailer = SmtpMailer(mail_from, user, passwd, mail_server,
31 mail_port, ssl, tls)
43 mail_port, ssl, tls)
32 mailer.send(recipients, subject, body, attachment_files)
44 mailer.send(recipients, subject, body, attachment_files)
33
45
34 :param recipients might be a list of string or single string
46 :param recipients might be a list of string or single string
35 :param attachment_files is a dict of {filename:location}
47 :param attachment_files is a dict of {filename:location}
36 it tries to guess the mimetype and attach the file
48 it tries to guess the mimetype and attach the file
37
49
38 """
50 """
39
51
40 def __init__(self, mail_from, user, passwd, mail_server,
52 def __init__(self, mail_from, user, passwd, mail_server,
41 mail_port=None, ssl=False, tls=False, debug=False):
53 mail_port=None, ssl=False, tls=False, debug=False):
42
54
43 self.mail_from = mail_from
55 self.mail_from = mail_from
44 self.mail_server = mail_server
56 self.mail_server = mail_server
45 self.mail_port = mail_port
57 self.mail_port = mail_port
46 self.user = user
58 self.user = user
47 self.passwd = passwd
59 self.passwd = passwd
48 self.ssl = ssl
60 self.ssl = ssl
49 self.tls = tls
61 self.tls = tls
50 self.debug = debug
62 self.debug = debug
51
63
52 def send(self, recipients=[], subject='', body='', attachment_files=None):
64 def send(self, recipients=[], subject='', body='', attachment_files=None):
53
65
54 if isinstance(recipients, basestring):
66 if isinstance(recipients, basestring):
55 recipients = [recipients]
67 recipients = [recipients]
56 if self.ssl:
68 if self.ssl:
57 smtp_serv = smtplib.SMTP_SSL(self.mail_server, self.mail_port)
69 smtp_serv = smtplib.SMTP_SSL(self.mail_server, self.mail_port)
58 else:
70 else:
59 smtp_serv = smtplib.SMTP(self.mail_server, self.mail_port)
71 smtp_serv = smtplib.SMTP(self.mail_server, self.mail_port)
60
72
61 if self.tls:
73 if self.tls:
62 smtp_serv.ehlo()
74 smtp_serv.ehlo()
63 smtp_serv.starttls()
75 smtp_serv.starttls()
64
76
65 if self.debug:
77 if self.debug:
66 smtp_serv.set_debuglevel(1)
78 smtp_serv.set_debuglevel(1)
67
79
68 smtp_serv.ehlo()
80 smtp_serv.ehlo()
69
81
70 #if server requires authorization you must provide login and password
82 #if server requires authorization you must provide login and password
71 #but only if we have them
83 #but only if we have them
72 if self.user and self.passwd:
84 if self.user and self.passwd:
73 smtp_serv.login(self.user, self.passwd)
85 smtp_serv.login(self.user, self.passwd)
74
86
75 date_ = formatdate(localtime=True)
87 date_ = formatdate(localtime=True)
76 msg = MIMEMultipart()
88 msg = MIMEMultipart()
77 msg.set_type('multipart/alternative')
89 msg.set_type('multipart/alternative')
78 msg.preamble = 'You will not see this in a MIME-aware mail reader.\n'
90 msg.preamble = 'You will not see this in a MIME-aware mail reader.\n'
79
91
80 text_msg = MIMEText(body)
92 text_msg = MIMEText(body)
81 text_msg.set_type('text/plain')
93 text_msg.set_type('text/plain')
82 text_msg.set_param('charset', 'UTF-8')
94 text_msg.set_param('charset', 'UTF-8')
83
95
84 msg['From'] = self.mail_from
96 msg['From'] = self.mail_from
85 msg['To'] = ','.join(recipients)
97 msg['To'] = ','.join(recipients)
86 msg['Date'] = date_
98 msg['Date'] = date_
87 msg['Subject'] = subject
99 msg['Subject'] = subject
88
100
89 msg.attach(text_msg)
101 msg.attach(text_msg)
90
102
91 if attachment_files:
103 if attachment_files:
92 self.__atach_files(msg, attachment_files)
104 self.__atach_files(msg, attachment_files)
93
105
94 smtp_serv.sendmail(self.mail_from, recipients, msg.as_string())
106 smtp_serv.sendmail(self.mail_from, recipients, msg.as_string())
95 logging.info('MAIL SEND TO: %s' % recipients)
107 logging.info('MAIL SEND TO: %s' % recipients)
96
108
97 try:
109 try:
98 smtp_serv.quit()
110 smtp_serv.quit()
99 except sslerror:
111 except sslerror:
100 # sslerror is raised in tls connections on closing sometimes
112 # sslerror is raised in tls connections on closing sometimes
101 pass
113 pass
102
114
103 def __atach_files(self, msg, attachment_files):
115 def __atach_files(self, msg, attachment_files):
104 if isinstance(attachment_files, dict):
116 if isinstance(attachment_files, dict):
105 for f_name, msg_file in attachment_files.items():
117 for f_name, msg_file in attachment_files.items():
106 ctype, encoding = mimetypes.guess_type(f_name)
118 ctype, encoding = mimetypes.guess_type(f_name)
107 logging.info("guessing file %s type based on %s", ctype,
119 logging.info("guessing file %s type based on %s", ctype,
108 f_name)
120 f_name)
109 if ctype is None or encoding is not None:
121 if ctype is None or encoding is not None:
110 # No guess could be made, or the file is encoded
122 # No guess could be made, or the file is encoded
111 # (compressed), so use a generic bag-of-bits type.
123 # (compressed), so use a generic bag-of-bits type.
112 ctype = 'application/octet-stream'
124 ctype = 'application/octet-stream'
113 maintype, subtype = ctype.split('/', 1)
125 maintype, subtype = ctype.split('/', 1)
114 if maintype == 'text':
126 if maintype == 'text':
115 # Note: we should handle calculating the charset
127 # Note: we should handle calculating the charset
116 file_part = MIMEText(self.get_content(msg_file),
128 file_part = MIMEText(self.get_content(msg_file),
117 _subtype=subtype)
129 _subtype=subtype)
118 elif maintype == 'image':
130 elif maintype == 'image':
119 file_part = MIMEImage(self.get_content(msg_file),
131 file_part = MIMEImage(self.get_content(msg_file),
120 _subtype=subtype)
132 _subtype=subtype)
121 elif maintype == 'audio':
133 elif maintype == 'audio':
122 file_part = MIMEAudio(self.get_content(msg_file),
134 file_part = MIMEAudio(self.get_content(msg_file),
123 _subtype=subtype)
135 _subtype=subtype)
124 else:
136 else:
125 file_part = MIMEBase(maintype, subtype)
137 file_part = MIMEBase(maintype, subtype)
126 file_part.set_payload(self.get_content(msg_file))
138 file_part.set_payload(self.get_content(msg_file))
127 # Encode the payload using Base64
139 # Encode the payload using Base64
128 encoders.encode_base64(msg)
140 encoders.encode_base64(msg)
129 # Set the filename parameter
141 # Set the filename parameter
130 file_part.add_header('Content-Disposition', 'attachment',
142 file_part.add_header('Content-Disposition', 'attachment',
131 filename=f_name)
143 filename=f_name)
132 file_part.add_header('Content-Type', ctype, name=f_name)
144 file_part.add_header('Content-Type', ctype, name=f_name)
133 msg.attach(file_part)
145 msg.attach(file_part)
134 else:
146 else:
135 raise Exception('Attachment files should be'
147 raise Exception('Attachment files should be'
136 'a dict in format {"filename":"filepath"}')
148 'a dict in format {"filename":"filepath"}')
137
149
138 def get_content(self, msg_file):
150 def get_content(self, msg_file):
139 """Get content based on type, if content is a string do open first
151 """Get content based on type, if content is a string do open first
140 else just read because it's a probably open file object
152 else just read because it's a probably open file object
141
153
142 :param msg_file:
154 :param msg_file:
143 """
155 """
144 if isinstance(msg_file, str):
156 if isinstance(msg_file, str):
145 return open(msg_file, "rb").read()
157 return open(msg_file, "rb").read()
146 else:
158 else:
147 #just for safe seek to 0
159 #just for safe seek to 0
148 msg_file.seek(0)
160 msg_file.seek(0)
149 return msg_file.read()
161 return msg_file.read()
@@ -1,611 +1,599
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.utils
3 rhodecode.lib.utils
4 ~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~
5
5
6 Utilities library for RhodeCode
6 Utilities library for RhodeCode
7
7
8 :created_on: Apr 18, 2010
8 :created_on: Apr 18, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import os
26 import os
27 import logging
27 import logging
28 import datetime
28 import datetime
29 import traceback
29 import traceback
30 import paste
30 import paste
31 import beaker
31 import beaker
32 from os.path import dirname as dn, join as jn
32 from os.path import dirname as dn, join as jn
33
33
34 from paste.script.command import Command, BadCommand
34 from paste.script.command import Command, BadCommand
35
35
36 from mercurial import ui, config
36 from mercurial import ui, config
37
37
38 from webhelpers.text import collapse, remove_formatting, strip_tags
38 from webhelpers.text import collapse, remove_formatting, strip_tags
39
39
40 from vcs import get_backend
40 from vcs import get_backend
41 from vcs.backends.base import BaseChangeset
41 from vcs.backends.base import BaseChangeset
42 from vcs.utils.lazy import LazyProperty
42 from vcs.utils.lazy import LazyProperty
43 from vcs.utils.helpers import get_scm
43 from vcs.utils.helpers import get_scm
44 from vcs.exceptions import VCSError
44 from vcs.exceptions import VCSError
45
45
46 from rhodecode.model import meta
46 from rhodecode.model import meta
47 from rhodecode.model.caching_query import FromCache
47 from rhodecode.model.caching_query import FromCache
48 from rhodecode.model.db import Repository, User, RhodeCodeUi, UserLog, Group, \
48 from rhodecode.model.db import Repository, User, RhodeCodeUi, UserLog, Group, \
49 RhodeCodeSettings
49 RhodeCodeSettings
50 from rhodecode.model.repo import RepoModel
50 from rhodecode.model.repo import RepoModel
51
51
52 log = logging.getLogger(__name__)
52 log = logging.getLogger(__name__)
53
53
54
54
55 def recursive_replace(str, replace=' '):
55 def recursive_replace(str, replace=' '):
56 """Recursive replace of given sign to just one instance
56 """Recursive replace of given sign to just one instance
57
57
58 :param str: given string
58 :param str: given string
59 :param replace: char to find and replace multiple instances
59 :param replace: char to find and replace multiple instances
60
60
61 Examples::
61 Examples::
62 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
62 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
63 'Mighty-Mighty-Bo-sstones'
63 'Mighty-Mighty-Bo-sstones'
64 """
64 """
65
65
66 if str.find(replace * 2) == -1:
66 if str.find(replace * 2) == -1:
67 return str
67 return str
68 else:
68 else:
69 str = str.replace(replace * 2, replace)
69 str = str.replace(replace * 2, replace)
70 return recursive_replace(str, replace)
70 return recursive_replace(str, replace)
71
71
72
72
73 def repo_name_slug(value):
73 def repo_name_slug(value):
74 """Return slug of name of repository
74 """Return slug of name of repository
75 This function is called on each creation/modification
75 This function is called on each creation/modification
76 of repository to prevent bad names in repo
76 of repository to prevent bad names in repo
77 """
77 """
78
78
79 slug = remove_formatting(value)
79 slug = remove_formatting(value)
80 slug = strip_tags(slug)
80 slug = strip_tags(slug)
81
81
82 for c in """=[]\;'"<>,/~!@#$%^&*()+{}|: """:
82 for c in """=[]\;'"<>,/~!@#$%^&*()+{}|: """:
83 slug = slug.replace(c, '-')
83 slug = slug.replace(c, '-')
84 slug = recursive_replace(slug, '-')
84 slug = recursive_replace(slug, '-')
85 slug = collapse(slug, '-')
85 slug = collapse(slug, '-')
86 return slug
86 return slug
87
87
88
88
89 def get_repo_slug(request):
89 def get_repo_slug(request):
90 return request.environ['pylons.routes_dict'].get('repo_name')
90 return request.environ['pylons.routes_dict'].get('repo_name')
91
91
92
92
93 def action_logger(user, action, repo, ipaddr='', sa=None):
93 def action_logger(user, action, repo, ipaddr='', sa=None):
94 """
94 """
95 Action logger for various actions made by users
95 Action logger for various actions made by users
96
96
97 :param user: user that made this action, can be a unique username string or
97 :param user: user that made this action, can be a unique username string or
98 object containing user_id attribute
98 object containing user_id attribute
99 :param action: action to log, should be on of predefined unique actions for
99 :param action: action to log, should be on of predefined unique actions for
100 easy translations
100 easy translations
101 :param repo: string name of repository or object containing repo_id,
101 :param repo: string name of repository or object containing repo_id,
102 that action was made on
102 that action was made on
103 :param ipaddr: optional ip address from what the action was made
103 :param ipaddr: optional ip address from what the action was made
104 :param sa: optional sqlalchemy session
104 :param sa: optional sqlalchemy session
105
105
106 """
106 """
107
107
108 if not sa:
108 if not sa:
109 sa = meta.Session()
109 sa = meta.Session()
110
110
111 try:
111 try:
112 if hasattr(user, 'user_id'):
112 if hasattr(user, 'user_id'):
113 user_obj = user
113 user_obj = user
114 elif isinstance(user, basestring):
114 elif isinstance(user, basestring):
115 user_obj = User.by_username(user)
115 user_obj = User.get_by_username(user)
116 else:
116 else:
117 raise Exception('You have to provide user object or username')
117 raise Exception('You have to provide user object or username')
118
118
119 rm = RepoModel()
119 rm = RepoModel()
120 if hasattr(repo, 'repo_id'):
120 if hasattr(repo, 'repo_id'):
121 repo_obj = rm.get(repo.repo_id, cache=False)
121 repo_obj = rm.get(repo.repo_id, cache=False)
122 repo_name = repo_obj.repo_name
122 repo_name = repo_obj.repo_name
123 elif isinstance(repo, basestring):
123 elif isinstance(repo, basestring):
124 repo_name = repo.lstrip('/')
124 repo_name = repo.lstrip('/')
125 repo_obj = rm.get_by_repo_name(repo_name, cache=False)
125 repo_obj = rm.get_by_repo_name(repo_name, cache=False)
126 else:
126 else:
127 raise Exception('You have to provide repository to action logger')
127 raise Exception('You have to provide repository to action logger')
128
128
129 user_log = UserLog()
129 user_log = UserLog()
130 user_log.user_id = user_obj.user_id
130 user_log.user_id = user_obj.user_id
131 user_log.action = action
131 user_log.action = action
132
132
133 user_log.repository_id = repo_obj.repo_id
133 user_log.repository_id = repo_obj.repo_id
134 user_log.repository_name = repo_name
134 user_log.repository_name = repo_name
135
135
136 user_log.action_date = datetime.datetime.now()
136 user_log.action_date = datetime.datetime.now()
137 user_log.user_ip = ipaddr
137 user_log.user_ip = ipaddr
138 sa.add(user_log)
138 sa.add(user_log)
139 sa.commit()
139 sa.commit()
140
140
141 log.info('Adding user %s, action %s on %s', user_obj, action, repo)
141 log.info('Adding user %s, action %s on %s', user_obj, action, repo)
142 except:
142 except:
143 log.error(traceback.format_exc())
143 log.error(traceback.format_exc())
144 sa.rollback()
144 sa.rollback()
145
145
146
146
147 def get_repos(path, recursive=False):
147 def get_repos(path, recursive=False):
148 """
148 """
149 Scans given path for repos and return (name,(type,path)) tuple
149 Scans given path for repos and return (name,(type,path)) tuple
150
150
151 :param path: path to scann for repositories
151 :param path: path to scann for repositories
152 :param recursive: recursive search and return names with subdirs in front
152 :param recursive: recursive search and return names with subdirs in front
153 """
153 """
154 from vcs.utils.helpers import get_scm
154 from vcs.utils.helpers import get_scm
155 from vcs.exceptions import VCSError
155 from vcs.exceptions import VCSError
156
156
157 if path.endswith(os.sep):
157 if path.endswith(os.sep):
158 #remove ending slash for better results
158 #remove ending slash for better results
159 path = path[:-1]
159 path = path[:-1]
160
160
161 def _get_repos(p):
161 def _get_repos(p):
162 if not os.access(p, os.W_OK):
162 if not os.access(p, os.W_OK):
163 return
163 return
164 for dirpath in os.listdir(p):
164 for dirpath in os.listdir(p):
165 if os.path.isfile(os.path.join(p, dirpath)):
165 if os.path.isfile(os.path.join(p, dirpath)):
166 continue
166 continue
167 cur_path = os.path.join(p, dirpath)
167 cur_path = os.path.join(p, dirpath)
168 try:
168 try:
169 scm_info = get_scm(cur_path)
169 scm_info = get_scm(cur_path)
170 yield scm_info[1].split(path)[-1].lstrip(os.sep), scm_info
170 yield scm_info[1].split(path)[-1].lstrip(os.sep), scm_info
171 except VCSError:
171 except VCSError:
172 if not recursive:
172 if not recursive:
173 continue
173 continue
174 #check if this dir containts other repos for recursive scan
174 #check if this dir containts other repos for recursive scan
175 rec_path = os.path.join(p, dirpath)
175 rec_path = os.path.join(p, dirpath)
176 if os.path.isdir(rec_path):
176 if os.path.isdir(rec_path):
177 for inner_scm in _get_repos(rec_path):
177 for inner_scm in _get_repos(rec_path):
178 yield inner_scm
178 yield inner_scm
179
179
180 return _get_repos(path)
180 return _get_repos(path)
181
181
182
182
183 def is_valid_repo(repo_name, base_path):
183 def is_valid_repo(repo_name, base_path):
184 """
184 """
185 Returns True if given path is a valid repository False otherwise
185 Returns True if given path is a valid repository False otherwise
186 :param repo_name:
186 :param repo_name:
187 :param base_path:
187 :param base_path:
188
188
189 :return True: if given path is a valid repository
189 :return True: if given path is a valid repository
190 """
190 """
191 full_path = os.path.join(base_path, repo_name)
191 full_path = os.path.join(base_path, repo_name)
192
192
193 try:
193 try:
194 get_scm(full_path)
194 get_scm(full_path)
195 return True
195 return True
196 except VCSError:
196 except VCSError:
197 return False
197 return False
198
198
199 def is_valid_repos_group(repos_group_name, base_path):
199 def is_valid_repos_group(repos_group_name, base_path):
200 """
200 """
201 Returns True if given path is a repos group False otherwise
201 Returns True if given path is a repos group False otherwise
202
202
203 :param repo_name:
203 :param repo_name:
204 :param base_path:
204 :param base_path:
205 """
205 """
206 full_path = os.path.join(base_path, repos_group_name)
206 full_path = os.path.join(base_path, repos_group_name)
207
207
208 # check if it's not a repo
208 # check if it's not a repo
209 if is_valid_repo(repos_group_name, base_path):
209 if is_valid_repo(repos_group_name, base_path):
210 return False
210 return False
211
211
212 # check if it's a valid path
212 # check if it's a valid path
213 if os.path.isdir(full_path):
213 if os.path.isdir(full_path):
214 return True
214 return True
215
215
216 return False
216 return False
217
217
218 def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
218 def ask_ok(prompt, retries=4, complaint='Yes or no, please!'):
219 while True:
219 while True:
220 ok = raw_input(prompt)
220 ok = raw_input(prompt)
221 if ok in ('y', 'ye', 'yes'):
221 if ok in ('y', 'ye', 'yes'):
222 return True
222 return True
223 if ok in ('n', 'no', 'nop', 'nope'):
223 if ok in ('n', 'no', 'nop', 'nope'):
224 return False
224 return False
225 retries = retries - 1
225 retries = retries - 1
226 if retries < 0:
226 if retries < 0:
227 raise IOError
227 raise IOError
228 print complaint
228 print complaint
229
229
230 #propagated from mercurial documentation
230 #propagated from mercurial documentation
231 ui_sections = ['alias', 'auth',
231 ui_sections = ['alias', 'auth',
232 'decode/encode', 'defaults',
232 'decode/encode', 'defaults',
233 'diff', 'email',
233 'diff', 'email',
234 'extensions', 'format',
234 'extensions', 'format',
235 'merge-patterns', 'merge-tools',
235 'merge-patterns', 'merge-tools',
236 'hooks', 'http_proxy',
236 'hooks', 'http_proxy',
237 'smtp', 'patch',
237 'smtp', 'patch',
238 'paths', 'profiling',
238 'paths', 'profiling',
239 'server', 'trusted',
239 'server', 'trusted',
240 'ui', 'web', ]
240 'ui', 'web', ]
241
241
242
242
243 def make_ui(read_from='file', path=None, checkpaths=True):
243 def make_ui(read_from='file', path=None, checkpaths=True):
244 """A function that will read python rc files or database
244 """A function that will read python rc files or database
245 and make an mercurial ui object from read options
245 and make an mercurial ui object from read options
246
246
247 :param path: path to mercurial config file
247 :param path: path to mercurial config file
248 :param checkpaths: check the path
248 :param checkpaths: check the path
249 :param read_from: read from 'file' or 'db'
249 :param read_from: read from 'file' or 'db'
250 """
250 """
251
251
252 baseui = ui.ui()
252 baseui = ui.ui()
253
253
254 #clean the baseui object
254 #clean the baseui object
255 baseui._ocfg = config.config()
255 baseui._ocfg = config.config()
256 baseui._ucfg = config.config()
256 baseui._ucfg = config.config()
257 baseui._tcfg = config.config()
257 baseui._tcfg = config.config()
258
258
259 if read_from == 'file':
259 if read_from == 'file':
260 if not os.path.isfile(path):
260 if not os.path.isfile(path):
261 log.warning('Unable to read config file %s' % path)
261 log.warning('Unable to read config file %s' % path)
262 return False
262 return False
263 log.debug('reading hgrc from %s', path)
263 log.debug('reading hgrc from %s', path)
264 cfg = config.config()
264 cfg = config.config()
265 cfg.read(path)
265 cfg.read(path)
266 for section in ui_sections:
266 for section in ui_sections:
267 for k, v in cfg.items(section):
267 for k, v in cfg.items(section):
268 log.debug('settings ui from file[%s]%s:%s', section, k, v)
268 log.debug('settings ui from file[%s]%s:%s', section, k, v)
269 baseui.setconfig(section, k, v)
269 baseui.setconfig(section, k, v)
270
270
271 elif read_from == 'db':
271 elif read_from == 'db':
272 sa = meta.Session()
272 sa = meta.Session()
273 ret = sa.query(RhodeCodeUi)\
273 ret = sa.query(RhodeCodeUi)\
274 .options(FromCache("sql_cache_short",
274 .options(FromCache("sql_cache_short",
275 "get_hg_ui_settings")).all()
275 "get_hg_ui_settings")).all()
276
276
277 hg_ui = ret
277 hg_ui = ret
278 for ui_ in hg_ui:
278 for ui_ in hg_ui:
279 if ui_.ui_active:
279 if ui_.ui_active:
280 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
280 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
281 ui_.ui_key, ui_.ui_value)
281 ui_.ui_key, ui_.ui_value)
282 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
282 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
283
283
284 meta.Session.remove()
284 meta.Session.remove()
285 return baseui
285 return baseui
286
286
287
287
288 def set_rhodecode_config(config):
288 def set_rhodecode_config(config):
289 """Updates pylons config with new settings from database
289 """Updates pylons config with new settings from database
290
290
291 :param config:
291 :param config:
292 """
292 """
293 hgsettings = RhodeCodeSettings.get_app_settings()
293 hgsettings = RhodeCodeSettings.get_app_settings()
294
294
295 for k, v in hgsettings.items():
295 for k, v in hgsettings.items():
296 config[k] = v
296 config[k] = v
297
297
298
298
299 def invalidate_cache(cache_key, *args):
299 def invalidate_cache(cache_key, *args):
300 """Puts cache invalidation task into db for
300 """Puts cache invalidation task into db for
301 further global cache invalidation
301 further global cache invalidation
302 """
302 """
303
303
304 from rhodecode.model.scm import ScmModel
304 from rhodecode.model.scm import ScmModel
305
305
306 if cache_key.startswith('get_repo_cached_'):
306 if cache_key.startswith('get_repo_cached_'):
307 name = cache_key.split('get_repo_cached_')[-1]
307 name = cache_key.split('get_repo_cached_')[-1]
308 ScmModel().mark_for_invalidation(name)
308 ScmModel().mark_for_invalidation(name)
309
309
310
310
311 class EmptyChangeset(BaseChangeset):
311 class EmptyChangeset(BaseChangeset):
312 """
312 """
313 An dummy empty changeset. It's possible to pass hash when creating
313 An dummy empty changeset. It's possible to pass hash when creating
314 an EmptyChangeset
314 an EmptyChangeset
315 """
315 """
316
316
317 def __init__(self, cs='0' * 40, repo=None, requested_revision=None, alias=None):
317 def __init__(self, cs='0' * 40, repo=None, requested_revision=None, alias=None):
318 self._empty_cs = cs
318 self._empty_cs = cs
319 self.revision = -1
319 self.revision = -1
320 self.message = ''
320 self.message = ''
321 self.author = ''
321 self.author = ''
322 self.date = ''
322 self.date = ''
323 self.repository = repo
323 self.repository = repo
324 self.requested_revision = requested_revision
324 self.requested_revision = requested_revision
325 self.alias = alias
325 self.alias = alias
326
326
327 @LazyProperty
327 @LazyProperty
328 def raw_id(self):
328 def raw_id(self):
329 """Returns raw string identifying this changeset, useful for web
329 """Returns raw string identifying this changeset, useful for web
330 representation.
330 representation.
331 """
331 """
332
332
333 return self._empty_cs
333 return self._empty_cs
334
334
335 @LazyProperty
335 @LazyProperty
336 def branch(self):
336 def branch(self):
337 return get_backend(self.alias).DEFAULT_BRANCH_NAME
337 return get_backend(self.alias).DEFAULT_BRANCH_NAME
338
338
339 @LazyProperty
339 @LazyProperty
340 def short_id(self):
340 def short_id(self):
341 return self.raw_id[:12]
341 return self.raw_id[:12]
342
342
343 def get_file_changeset(self, path):
343 def get_file_changeset(self, path):
344 return self
344 return self
345
345
346 def get_file_content(self, path):
346 def get_file_content(self, path):
347 return u''
347 return u''
348
348
349 def get_file_size(self, path):
349 def get_file_size(self, path):
350 return 0
350 return 0
351
351
352
352
353 def map_groups(groups):
353 def map_groups(groups):
354 """Checks for groups existence, and creates groups structures.
354 """Checks for groups existence, and creates groups structures.
355 It returns last group in structure
355 It returns last group in structure
356
356
357 :param groups: list of groups structure
357 :param groups: list of groups structure
358 """
358 """
359 sa = meta.Session()
359 sa = meta.Session()
360
360
361 parent = None
361 parent = None
362 group = None
362 group = None
363 for lvl, group_name in enumerate(groups[:-1]):
363
364 # last element is repo in nested groups structure
365 groups = groups[:-1]
366
367 for lvl, group_name in enumerate(groups):
368 group_name = '/'.join(groups[:lvl] + [group_name])
364 group = sa.query(Group).filter(Group.group_name == group_name).scalar()
369 group = sa.query(Group).filter(Group.group_name == group_name).scalar()
365
370
366 if group is None:
371 if group is None:
367 group = Group(group_name, parent)
372 group = Group(group_name, parent)
368 sa.add(group)
373 sa.add(group)
369 sa.commit()
374 sa.commit()
370
371 parent = group
375 parent = group
372
373 return group
376 return group
374
377
375
378
376 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
379 def repo2db_mapper(initial_repo_list, remove_obsolete=False):
377 """maps all repos given in initial_repo_list, non existing repositories
380 """maps all repos given in initial_repo_list, non existing repositories
378 are created, if remove_obsolete is True it also check for db entries
381 are created, if remove_obsolete is True it also check for db entries
379 that are not in initial_repo_list and removes them.
382 that are not in initial_repo_list and removes them.
380
383
381 :param initial_repo_list: list of repositories found by scanning methods
384 :param initial_repo_list: list of repositories found by scanning methods
382 :param remove_obsolete: check for obsolete entries in database
385 :param remove_obsolete: check for obsolete entries in database
383 """
386 """
384
387
385 sa = meta.Session()
388 sa = meta.Session()
386 rm = RepoModel()
389 rm = RepoModel()
387 user = sa.query(User).filter(User.admin == True).first()
390 user = sa.query(User).filter(User.admin == True).first()
388 added = []
391 added = []
392 # fixup groups paths to new format on the fly
393 # TODO: remove this in future
394 for g in Group.query().all():
395 g.group_name = g.get_new_name(g.name)
396 sa.add(g)
389 for name, repo in initial_repo_list.items():
397 for name, repo in initial_repo_list.items():
390 group = map_groups(name.split(os.sep))
398 group = map_groups(name.split(Repository.url_sep()))
391 if not rm.get_by_repo_name(name, cache=False):
399 if not rm.get_by_repo_name(name, cache=False):
392 log.info('repository %s not found creating default', name)
400 log.info('repository %s not found creating default', name)
393 added.append(name)
401 added.append(name)
394 form_data = {
402 form_data = {
395 'repo_name': name,
403 'repo_name': name,
396 'repo_name_full': name,
404 'repo_name_full': name,
397 'repo_type': repo.alias,
405 'repo_type': repo.alias,
398 'description': repo.description \
406 'description': repo.description \
399 if repo.description != 'unknown' else \
407 if repo.description != 'unknown' else \
400 '%s repository' % name,
408 '%s repository' % name,
401 'private': False,
409 'private': False,
402 'group_id': getattr(group, 'group_id', None)
410 'group_id': getattr(group, 'group_id', None)
403 }
411 }
404 rm.create(form_data, user, just_db=True)
412 rm.create(form_data, user, just_db=True)
405
413
406 removed = []
414 removed = []
407 if remove_obsolete:
415 if remove_obsolete:
408 #remove from database those repositories that are not in the filesystem
416 #remove from database those repositories that are not in the filesystem
409 for repo in sa.query(Repository).all():
417 for repo in sa.query(Repository).all():
410 if repo.repo_name not in initial_repo_list.keys():
418 if repo.repo_name not in initial_repo_list.keys():
411 removed.append(repo.repo_name)
419 removed.append(repo.repo_name)
412 sa.delete(repo)
420 sa.delete(repo)
413 sa.commit()
421 sa.commit()
414
422
415 return added, removed
423 return added, removed
416
424
417 #set cache regions for beaker so celery can utilise it
425 #set cache regions for beaker so celery can utilise it
418 def add_cache(settings):
426 def add_cache(settings):
419 cache_settings = {'regions': None}
427 cache_settings = {'regions': None}
420 for key in settings.keys():
428 for key in settings.keys():
421 for prefix in ['beaker.cache.', 'cache.']:
429 for prefix in ['beaker.cache.', 'cache.']:
422 if key.startswith(prefix):
430 if key.startswith(prefix):
423 name = key.split(prefix)[1].strip()
431 name = key.split(prefix)[1].strip()
424 cache_settings[name] = settings[key].strip()
432 cache_settings[name] = settings[key].strip()
425 if cache_settings['regions']:
433 if cache_settings['regions']:
426 for region in cache_settings['regions'].split(','):
434 for region in cache_settings['regions'].split(','):
427 region = region.strip()
435 region = region.strip()
428 region_settings = {}
436 region_settings = {}
429 for key, value in cache_settings.items():
437 for key, value in cache_settings.items():
430 if key.startswith(region):
438 if key.startswith(region):
431 region_settings[key.split('.')[1]] = value
439 region_settings[key.split('.')[1]] = value
432 region_settings['expire'] = int(region_settings.get('expire',
440 region_settings['expire'] = int(region_settings.get('expire',
433 60))
441 60))
434 region_settings.setdefault('lock_dir',
442 region_settings.setdefault('lock_dir',
435 cache_settings.get('lock_dir'))
443 cache_settings.get('lock_dir'))
436 region_settings.setdefault('data_dir',
444 region_settings.setdefault('data_dir',
437 cache_settings.get('data_dir'))
445 cache_settings.get('data_dir'))
438
446
439 if 'type' not in region_settings:
447 if 'type' not in region_settings:
440 region_settings['type'] = cache_settings.get('type',
448 region_settings['type'] = cache_settings.get('type',
441 'memory')
449 'memory')
442 beaker.cache.cache_regions[region] = region_settings
450 beaker.cache.cache_regions[region] = region_settings
443
451
444
452
445 def get_current_revision():
446 """Returns tuple of (number, id) from repository containing this package
447 or None if repository could not be found.
448 """
449
450 try:
451 from vcs import get_repo
452 from vcs.utils.helpers import get_scm
453 from vcs.exceptions import RepositoryError, VCSError
454 repopath = os.path.join(os.path.dirname(__file__), '..', '..')
455 scm = get_scm(repopath)[0]
456 repo = get_repo(path=repopath, alias=scm)
457 tip = repo.get_changeset()
458 return (tip.revision, tip.short_id)
459 except (ImportError, RepositoryError, VCSError), err:
460 logging.debug("Cannot retrieve rhodecode's revision. Original error "
461 "was: %s" % err)
462 return None
463
464
465 #==============================================================================
453 #==============================================================================
466 # TEST FUNCTIONS AND CREATORS
454 # TEST FUNCTIONS AND CREATORS
467 #==============================================================================
455 #==============================================================================
468 def create_test_index(repo_location, config, full_index):
456 def create_test_index(repo_location, config, full_index):
469 """
457 """
470 Makes default test index
458 Makes default test index
471
459
472 :param config: test config
460 :param config: test config
473 :param full_index:
461 :param full_index:
474 """
462 """
475
463
476 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
464 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
477 from rhodecode.lib.pidlock import DaemonLock, LockHeld
465 from rhodecode.lib.pidlock import DaemonLock, LockHeld
478
466
479 repo_location = repo_location
467 repo_location = repo_location
480
468
481 index_location = os.path.join(config['app_conf']['index_dir'])
469 index_location = os.path.join(config['app_conf']['index_dir'])
482 if not os.path.exists(index_location):
470 if not os.path.exists(index_location):
483 os.makedirs(index_location)
471 os.makedirs(index_location)
484
472
485 try:
473 try:
486 l = DaemonLock(file=jn(dn(index_location), 'make_index.lock'))
474 l = DaemonLock(file_=jn(dn(index_location), 'make_index.lock'))
487 WhooshIndexingDaemon(index_location=index_location,
475 WhooshIndexingDaemon(index_location=index_location,
488 repo_location=repo_location)\
476 repo_location=repo_location)\
489 .run(full_index=full_index)
477 .run(full_index=full_index)
490 l.release()
478 l.release()
491 except LockHeld:
479 except LockHeld:
492 pass
480 pass
493
481
494
482
495 def create_test_env(repos_test_path, config):
483 def create_test_env(repos_test_path, config):
496 """Makes a fresh database and
484 """Makes a fresh database and
497 install test repository into tmp dir
485 install test repository into tmp dir
498 """
486 """
499 from rhodecode.lib.db_manage import DbManage
487 from rhodecode.lib.db_manage import DbManage
500 from rhodecode.tests import HG_REPO, GIT_REPO, NEW_HG_REPO, NEW_GIT_REPO, \
488 from rhodecode.tests import HG_REPO, GIT_REPO, NEW_HG_REPO, NEW_GIT_REPO, \
501 HG_FORK, GIT_FORK, TESTS_TMP_PATH
489 HG_FORK, GIT_FORK, TESTS_TMP_PATH
502 import tarfile
490 import tarfile
503 import shutil
491 import shutil
504 from os.path import abspath
492 from os.path import abspath
505
493
506 # PART ONE create db
494 # PART ONE create db
507 dbconf = config['sqlalchemy.db1.url']
495 dbconf = config['sqlalchemy.db1.url']
508 log.debug('making test db %s', dbconf)
496 log.debug('making test db %s', dbconf)
509
497
510 # create test dir if it doesn't exist
498 # create test dir if it doesn't exist
511 if not os.path.isdir(repos_test_path):
499 if not os.path.isdir(repos_test_path):
512 log.debug('Creating testdir %s' % repos_test_path)
500 log.debug('Creating testdir %s' % repos_test_path)
513 os.makedirs(repos_test_path)
501 os.makedirs(repos_test_path)
514
502
515 dbmanage = DbManage(log_sql=True, dbconf=dbconf, root=config['here'],
503 dbmanage = DbManage(log_sql=True, dbconf=dbconf, root=config['here'],
516 tests=True)
504 tests=True)
517 dbmanage.create_tables(override=True)
505 dbmanage.create_tables(override=True)
518 dbmanage.create_settings(dbmanage.config_prompt(repos_test_path))
506 dbmanage.create_settings(dbmanage.config_prompt(repos_test_path))
519 dbmanage.create_default_user()
507 dbmanage.create_default_user()
520 dbmanage.admin_prompt()
508 dbmanage.admin_prompt()
521 dbmanage.create_permissions()
509 dbmanage.create_permissions()
522 dbmanage.populate_default_permissions()
510 dbmanage.populate_default_permissions()
523
511
524 # PART TWO make test repo
512 # PART TWO make test repo
525 log.debug('making test vcs repositories')
513 log.debug('making test vcs repositories')
526
514
527 idx_path = config['app_conf']['index_dir']
515 idx_path = config['app_conf']['index_dir']
528 data_path = config['app_conf']['cache_dir']
516 data_path = config['app_conf']['cache_dir']
529
517
530 #clean index and data
518 #clean index and data
531 if idx_path and os.path.exists(idx_path):
519 if idx_path and os.path.exists(idx_path):
532 log.debug('remove %s' % idx_path)
520 log.debug('remove %s' % idx_path)
533 shutil.rmtree(idx_path)
521 shutil.rmtree(idx_path)
534
522
535 if data_path and os.path.exists(data_path):
523 if data_path and os.path.exists(data_path):
536 log.debug('remove %s' % data_path)
524 log.debug('remove %s' % data_path)
537 shutil.rmtree(data_path)
525 shutil.rmtree(data_path)
538
526
539 #CREATE DEFAULT HG REPOSITORY
527 #CREATE DEFAULT HG REPOSITORY
540 cur_dir = dn(dn(abspath(__file__)))
528 cur_dir = dn(dn(abspath(__file__)))
541 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_hg.tar.gz"))
529 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_hg.tar.gz"))
542 tar.extractall(jn(TESTS_TMP_PATH, HG_REPO))
530 tar.extractall(jn(TESTS_TMP_PATH, HG_REPO))
543 tar.close()
531 tar.close()
544
532
545
533
546 #==============================================================================
534 #==============================================================================
547 # PASTER COMMANDS
535 # PASTER COMMANDS
548 #==============================================================================
536 #==============================================================================
549 class BasePasterCommand(Command):
537 class BasePasterCommand(Command):
550 """
538 """
551 Abstract Base Class for paster commands.
539 Abstract Base Class for paster commands.
552
540
553 The celery commands are somewhat aggressive about loading
541 The celery commands are somewhat aggressive about loading
554 celery.conf, and since our module sets the `CELERY_LOADER`
542 celery.conf, and since our module sets the `CELERY_LOADER`
555 environment variable to our loader, we have to bootstrap a bit and
543 environment variable to our loader, we have to bootstrap a bit and
556 make sure we've had a chance to load the pylons config off of the
544 make sure we've had a chance to load the pylons config off of the
557 command line, otherwise everything fails.
545 command line, otherwise everything fails.
558 """
546 """
559 min_args = 1
547 min_args = 1
560 min_args_error = "Please provide a paster config file as an argument."
548 min_args_error = "Please provide a paster config file as an argument."
561 takes_config_file = 1
549 takes_config_file = 1
562 requires_config_file = True
550 requires_config_file = True
563
551
564 def notify_msg(self, msg, log=False):
552 def notify_msg(self, msg, log=False):
565 """Make a notification to user, additionally if logger is passed
553 """Make a notification to user, additionally if logger is passed
566 it logs this action using given logger
554 it logs this action using given logger
567
555
568 :param msg: message that will be printed to user
556 :param msg: message that will be printed to user
569 :param log: logging instance, to use to additionally log this message
557 :param log: logging instance, to use to additionally log this message
570
558
571 """
559 """
572 if log and isinstance(log, logging):
560 if log and isinstance(log, logging):
573 log(msg)
561 log(msg)
574
562
575 def run(self, args):
563 def run(self, args):
576 """
564 """
577 Overrides Command.run
565 Overrides Command.run
578
566
579 Checks for a config file argument and loads it.
567 Checks for a config file argument and loads it.
580 """
568 """
581 if len(args) < self.min_args:
569 if len(args) < self.min_args:
582 raise BadCommand(
570 raise BadCommand(
583 self.min_args_error % {'min_args': self.min_args,
571 self.min_args_error % {'min_args': self.min_args,
584 'actual_args': len(args)})
572 'actual_args': len(args)})
585
573
586 # Decrement because we're going to lob off the first argument.
574 # Decrement because we're going to lob off the first argument.
587 # @@ This is hacky
575 # @@ This is hacky
588 self.min_args -= 1
576 self.min_args -= 1
589 self.bootstrap_config(args[0])
577 self.bootstrap_config(args[0])
590 self.update_parser()
578 self.update_parser()
591 return super(BasePasterCommand, self).run(args[1:])
579 return super(BasePasterCommand, self).run(args[1:])
592
580
593 def update_parser(self):
581 def update_parser(self):
594 """
582 """
595 Abstract method. Allows for the class's parser to be updated
583 Abstract method. Allows for the class's parser to be updated
596 before the superclass's `run` method is called. Necessary to
584 before the superclass's `run` method is called. Necessary to
597 allow options/arguments to be passed through to the underlying
585 allow options/arguments to be passed through to the underlying
598 celery command.
586 celery command.
599 """
587 """
600 raise NotImplementedError("Abstract Method.")
588 raise NotImplementedError("Abstract Method.")
601
589
602 def bootstrap_config(self, conf):
590 def bootstrap_config(self, conf):
603 """
591 """
604 Loads the pylons configuration.
592 Loads the pylons configuration.
605 """
593 """
606 from pylons import config as pylonsconfig
594 from pylons import config as pylonsconfig
607
595
608 path_to_ini_file = os.path.realpath(conf)
596 path_to_ini_file = os.path.realpath(conf)
609 conf = paste.deploy.appconfig('config:' + path_to_ini_file)
597 conf = paste.deploy.appconfig('config:' + path_to_ini_file)
610 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
598 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
611
599
@@ -1,980 +1,1051
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.db
3 rhodecode.model.db
4 ~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~
5
5
6 Database Models for RhodeCode
6 Database Models for RhodeCode
7
7
8 :created_on: Apr 08, 2010
8 :created_on: Apr 08, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import os
26 import os
27 import logging
27 import logging
28 import datetime
28 import datetime
29 import traceback
29 import traceback
30 from datetime import date
30 from datetime import date
31
31
32 from sqlalchemy import *
32 from sqlalchemy import *
33 from sqlalchemy.exc import DatabaseError
33 from sqlalchemy.exc import DatabaseError
34 from sqlalchemy.orm import relationship, backref, joinedload, class_mapper
34 from sqlalchemy.ext.hybrid import hybrid_property
35 from sqlalchemy.orm import relationship, backref, joinedload, class_mapper, \
36 validates
35 from sqlalchemy.orm.interfaces import MapperExtension
37 from sqlalchemy.orm.interfaces import MapperExtension
36
37 from beaker.cache import cache_region, region_invalidate
38 from beaker.cache import cache_region, region_invalidate
38
39
39 from vcs import get_backend
40 from vcs import get_backend
40 from vcs.utils.helpers import get_scm
41 from vcs.utils.helpers import get_scm
41 from vcs.exceptions import VCSError
42 from vcs.exceptions import VCSError
42 from vcs.utils.lazy import LazyProperty
43 from vcs.utils.lazy import LazyProperty
43
44
44 from rhodecode.lib import str2bool, safe_str, get_changeset_safe, \
45 from rhodecode.lib import str2bool, safe_str, get_changeset_safe, \
45 generate_api_key
46 generate_api_key, safe_unicode
46 from rhodecode.lib.exceptions import UsersGroupsAssignedException
47 from rhodecode.lib.exceptions import UsersGroupsAssignedException
47 from rhodecode.lib.compat import json
48 from rhodecode.lib.compat import json
48
49
49 from rhodecode.model.meta import Base, Session
50 from rhodecode.model.meta import Base, Session
50 from rhodecode.model.caching_query import FromCache
51 from rhodecode.model.caching_query import FromCache
51
52
53
52 log = logging.getLogger(__name__)
54 log = logging.getLogger(__name__)
53
55
54 #==============================================================================
56 #==============================================================================
55 # BASE CLASSES
57 # BASE CLASSES
56 #==============================================================================
58 #==============================================================================
57
59
58 class ModelSerializer(json.JSONEncoder):
60 class ModelSerializer(json.JSONEncoder):
59 """
61 """
60 Simple Serializer for JSON,
62 Simple Serializer for JSON,
61
63
62 usage::
64 usage::
63
65
64 to make object customized for serialization implement a __json__
66 to make object customized for serialization implement a __json__
65 method that will return a dict for serialization into json
67 method that will return a dict for serialization into json
66
68
67 example::
69 example::
68
70
69 class Task(object):
71 class Task(object):
70
72
71 def __init__(self, name, value):
73 def __init__(self, name, value):
72 self.name = name
74 self.name = name
73 self.value = value
75 self.value = value
74
76
75 def __json__(self):
77 def __json__(self):
76 return dict(name=self.name,
78 return dict(name=self.name,
77 value=self.value)
79 value=self.value)
78
80
79 """
81 """
80
82
81 def default(self, obj):
83 def default(self, obj):
82
84
83 if hasattr(obj, '__json__'):
85 if hasattr(obj, '__json__'):
84 return obj.__json__()
86 return obj.__json__()
85 else:
87 else:
86 return json.JSONEncoder.default(self, obj)
88 return json.JSONEncoder.default(self, obj)
87
89
88 class BaseModel(object):
90 class BaseModel(object):
89 """Base Model for all classess
91 """Base Model for all classess
90
92
91 """
93 """
92
94
93 @classmethod
95 @classmethod
94 def _get_keys(cls):
96 def _get_keys(cls):
95 """return column names for this model """
97 """return column names for this model """
96 return class_mapper(cls).c.keys()
98 return class_mapper(cls).c.keys()
97
99
98 def get_dict(self):
100 def get_dict(self):
99 """return dict with keys and values corresponding
101 """return dict with keys and values corresponding
100 to this model data """
102 to this model data """
101
103
102 d = {}
104 d = {}
103 for k in self._get_keys():
105 for k in self._get_keys():
104 d[k] = getattr(self, k)
106 d[k] = getattr(self, k)
105 return d
107 return d
106
108
107 def get_appstruct(self):
109 def get_appstruct(self):
108 """return list with keys and values tupples corresponding
110 """return list with keys and values tupples corresponding
109 to this model data """
111 to this model data """
110
112
111 l = []
113 l = []
112 for k in self._get_keys():
114 for k in self._get_keys():
113 l.append((k, getattr(self, k),))
115 l.append((k, getattr(self, k),))
114 return l
116 return l
115
117
116 def populate_obj(self, populate_dict):
118 def populate_obj(self, populate_dict):
117 """populate model with data from given populate_dict"""
119 """populate model with data from given populate_dict"""
118
120
119 for k in self._get_keys():
121 for k in self._get_keys():
120 if k in populate_dict:
122 if k in populate_dict:
121 setattr(self, k, populate_dict[k])
123 setattr(self, k, populate_dict[k])
122
124
123 @classmethod
125 @classmethod
124 def query(cls):
126 def query(cls):
125 return Session.query(cls)
127 return Session.query(cls)
126
128
127 @classmethod
129 @classmethod
128 def get(cls, id_):
130 def get(cls, id_):
129 return Session.query(cls).get(id_)
131 if id_:
132 return Session.query(cls).get(id_)
130
133
131 @classmethod
134 @classmethod
132 def delete(cls, id_):
135 def delete(cls, id_):
133 obj = Session.query(cls).get(id_)
136 obj = Session.query(cls).get(id_)
134 Session.delete(obj)
137 Session.delete(obj)
135 Session.commit()
138 Session.commit()
136
139
137
140
138 class RhodeCodeSettings(Base, BaseModel):
141 class RhodeCodeSettings(Base, BaseModel):
139 __tablename__ = 'rhodecode_settings'
142 __tablename__ = 'rhodecode_settings'
140 __table_args__ = (UniqueConstraint('app_settings_name'), {'extend_existing':True})
143 __table_args__ = (UniqueConstraint('app_settings_name'), {'extend_existing':True})
141 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
144 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
142 app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
145 app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
143 app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
146 _app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
144
147
145 def __init__(self, k='', v=''):
148 def __init__(self, k='', v=''):
146 self.app_settings_name = k
149 self.app_settings_name = k
147 self.app_settings_value = v
150 self.app_settings_value = v
148
151
152
153 @validates('_app_settings_value')
154 def validate_settings_value(self, key, val):
155 assert type(val) == unicode
156 return val
157
158 @hybrid_property
159 def app_settings_value(self):
160 v = self._app_settings_value
161 if v == 'ldap_active':
162 v = str2bool(v)
163 return v
164
165 @app_settings_value.setter
166 def app_settings_value(self,val):
167 """
168 Setter that will always make sure we use unicode in app_settings_value
169
170 :param val:
171 """
172 self._app_settings_value = safe_unicode(val)
173
149 def __repr__(self):
174 def __repr__(self):
150 return "<%s('%s:%s')>" % (self.__class__.__name__,
175 return "<%s('%s:%s')>" % (self.__class__.__name__,
151 self.app_settings_name, self.app_settings_value)
176 self.app_settings_name, self.app_settings_value)
152
177
153
178
154 @classmethod
179 @classmethod
155 def get_by_name(cls, ldap_key):
180 def get_by_name(cls, ldap_key):
156 return Session.query(cls)\
181 return Session.query(cls)\
157 .filter(cls.app_settings_name == ldap_key).scalar()
182 .filter(cls.app_settings_name == ldap_key).scalar()
158
183
159 @classmethod
184 @classmethod
160 def get_app_settings(cls, cache=False):
185 def get_app_settings(cls, cache=False):
161
186
162 ret = Session.query(cls)
187 ret = Session.query(cls)
163
188
164 if cache:
189 if cache:
165 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
190 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
166
191
167 if not ret:
192 if not ret:
168 raise Exception('Could not get application settings !')
193 raise Exception('Could not get application settings !')
169 settings = {}
194 settings = {}
170 for each in ret:
195 for each in ret:
171 settings['rhodecode_' + each.app_settings_name] = \
196 settings['rhodecode_' + each.app_settings_name] = \
172 each.app_settings_value
197 each.app_settings_value
173
198
174 return settings
199 return settings
175
200
176 @classmethod
201 @classmethod
177 def get_ldap_settings(cls, cache=False):
202 def get_ldap_settings(cls, cache=False):
178 ret = Session.query(cls)\
203 ret = Session.query(cls)\
179 .filter(cls.app_settings_name.startswith('ldap_'))\
204 .filter(cls.app_settings_name.startswith('ldap_')).all()
180 .all()
181 fd = {}
205 fd = {}
182 for row in ret:
206 for row in ret:
183 fd.update({row.app_settings_name:row.app_settings_value})
207 fd.update({row.app_settings_name:row.app_settings_value})
184
208
185 fd.update({'ldap_active':str2bool(fd.get('ldap_active'))})
186
187 return fd
209 return fd
188
210
189
211
190 class RhodeCodeUi(Base, BaseModel):
212 class RhodeCodeUi(Base, BaseModel):
191 __tablename__ = 'rhodecode_ui'
213 __tablename__ = 'rhodecode_ui'
192 __table_args__ = (UniqueConstraint('ui_key'), {'extend_existing':True})
214 __table_args__ = (UniqueConstraint('ui_key'), {'extend_existing':True})
193
215
194 HOOK_UPDATE = 'changegroup.update'
216 HOOK_UPDATE = 'changegroup.update'
195 HOOK_REPO_SIZE = 'changegroup.repo_size'
217 HOOK_REPO_SIZE = 'changegroup.repo_size'
196 HOOK_PUSH = 'pretxnchangegroup.push_logger'
218 HOOK_PUSH = 'pretxnchangegroup.push_logger'
197 HOOK_PULL = 'preoutgoing.pull_logger'
219 HOOK_PULL = 'preoutgoing.pull_logger'
198
220
199 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
221 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
200 ui_section = Column("ui_section", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
222 ui_section = Column("ui_section", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
201 ui_key = Column("ui_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
223 ui_key = Column("ui_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
202 ui_value = Column("ui_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
224 ui_value = Column("ui_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
203 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
225 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
204
226
205
227
206 @classmethod
228 @classmethod
207 def get_by_key(cls, key):
229 def get_by_key(cls, key):
208 return Session.query(cls).filter(cls.ui_key == key)
230 return Session.query(cls).filter(cls.ui_key == key)
209
231
210
232
211 @classmethod
233 @classmethod
212 def get_builtin_hooks(cls):
234 def get_builtin_hooks(cls):
213 q = cls.query()
235 q = cls.query()
214 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE,
236 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE,
215 cls.HOOK_REPO_SIZE,
237 cls.HOOK_REPO_SIZE,
216 cls.HOOK_PUSH, cls.HOOK_PULL]))
238 cls.HOOK_PUSH, cls.HOOK_PULL]))
217 return q.all()
239 return q.all()
218
240
219 @classmethod
241 @classmethod
220 def get_custom_hooks(cls):
242 def get_custom_hooks(cls):
221 q = cls.query()
243 q = cls.query()
222 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE,
244 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE,
223 cls.HOOK_REPO_SIZE,
245 cls.HOOK_REPO_SIZE,
224 cls.HOOK_PUSH, cls.HOOK_PULL]))
246 cls.HOOK_PUSH, cls.HOOK_PULL]))
225 q = q.filter(cls.ui_section == 'hooks')
247 q = q.filter(cls.ui_section == 'hooks')
226 return q.all()
248 return q.all()
227
249
228 @classmethod
250 @classmethod
229 def create_or_update_hook(cls, key, val):
251 def create_or_update_hook(cls, key, val):
230 new_ui = cls.get_by_key(key).scalar() or cls()
252 new_ui = cls.get_by_key(key).scalar() or cls()
231 new_ui.ui_section = 'hooks'
253 new_ui.ui_section = 'hooks'
232 new_ui.ui_active = True
254 new_ui.ui_active = True
233 new_ui.ui_key = key
255 new_ui.ui_key = key
234 new_ui.ui_value = val
256 new_ui.ui_value = val
235
257
236 Session.add(new_ui)
258 Session.add(new_ui)
237 Session.commit()
259 Session.commit()
238
260
239
261
240 class User(Base, BaseModel):
262 class User(Base, BaseModel):
241 __tablename__ = 'users'
263 __tablename__ = 'users'
242 __table_args__ = (UniqueConstraint('username'), UniqueConstraint('email'), {'extend_existing':True})
264 __table_args__ = (UniqueConstraint('username'), UniqueConstraint('email'), {'extend_existing':True})
243 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
265 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
244 username = Column("username", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
266 username = Column("username", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
245 password = Column("password", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
267 password = Column("password", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
246 active = Column("active", Boolean(), nullable=True, unique=None, default=None)
268 active = Column("active", Boolean(), nullable=True, unique=None, default=None)
247 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
269 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
248 name = Column("name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
270 name = Column("name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
249 lastname = Column("lastname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
271 lastname = Column("lastname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
250 email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
272 email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
251 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
273 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
252 ldap_dn = Column("ldap_dn", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
274 ldap_dn = Column("ldap_dn", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
253 api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
275 api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
254
276
255 user_log = relationship('UserLog', cascade='all')
277 user_log = relationship('UserLog', cascade='all')
256 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
278 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
257
279
258 repositories = relationship('Repository')
280 repositories = relationship('Repository')
259 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
281 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
260 repo_to_perm = relationship('RepoToPerm', primaryjoin='RepoToPerm.user_id==User.user_id', cascade='all')
282 repo_to_perm = relationship('RepoToPerm', primaryjoin='RepoToPerm.user_id==User.user_id', cascade='all')
261
283
262 group_member = relationship('UsersGroupMember', cascade='all')
284 group_member = relationship('UsersGroupMember', cascade='all')
263
285
264 @property
286 @property
265 def full_contact(self):
287 def full_contact(self):
266 return '%s %s <%s>' % (self.name, self.lastname, self.email)
288 return '%s %s <%s>' % (self.name, self.lastname, self.email)
267
289
268 @property
290 @property
269 def short_contact(self):
291 def short_contact(self):
270 return '%s %s' % (self.name, self.lastname)
292 return '%s %s' % (self.name, self.lastname)
271
293
272 @property
294 @property
273 def is_admin(self):
295 def is_admin(self):
274 return self.admin
296 return self.admin
275
297
276 def __repr__(self):
298 def __repr__(self):
277 try:
299 try:
278 return "<%s('id:%s:%s')>" % (self.__class__.__name__,
300 return "<%s('id:%s:%s')>" % (self.__class__.__name__,
279 self.user_id, self.username)
301 self.user_id, self.username)
280 except:
302 except:
281 return self.__class__.__name__
303 return self.__class__.__name__
282
304
283 @classmethod
305 @classmethod
284 def by_username(cls, username, case_insensitive=False):
306 def get_by_username(cls, username, case_insensitive=False):
285 if case_insensitive:
307 if case_insensitive:
286 return Session.query(cls).filter(cls.username.like(username)).one()
308 return Session.query(cls).filter(cls.username.ilike(username)).scalar()
287 else:
309 else:
288 return Session.query(cls).filter(cls.username == username).one()
310 return Session.query(cls).filter(cls.username == username).scalar()
289
311
290 @classmethod
312 @classmethod
291 def get_by_api_key(cls, api_key):
313 def get_by_api_key(cls, api_key):
292 return Session.query(cls).filter(cls.api_key == api_key).one()
314 return Session.query(cls).filter(cls.api_key == api_key).one()
293
315
294
295 def update_lastlogin(self):
316 def update_lastlogin(self):
296 """Update user lastlogin"""
317 """Update user lastlogin"""
297
318
298 self.last_login = datetime.datetime.now()
319 self.last_login = datetime.datetime.now()
299 Session.add(self)
320 Session.add(self)
300 Session.commit()
321 Session.commit()
301 log.debug('updated user %s lastlogin', self.username)
322 log.debug('updated user %s lastlogin', self.username)
302
323
303 @classmethod
324 @classmethod
304 def create(cls, form_data):
325 def create(cls, form_data):
305 from rhodecode.lib.auth import get_crypt_password
326 from rhodecode.lib.auth import get_crypt_password
306
327
307 try:
328 try:
308 new_user = cls()
329 new_user = cls()
309 for k, v in form_data.items():
330 for k, v in form_data.items():
310 if k == 'password':
331 if k == 'password':
311 v = get_crypt_password(v)
332 v = get_crypt_password(v)
312 setattr(new_user, k, v)
333 setattr(new_user, k, v)
313
334
314 new_user.api_key = generate_api_key(form_data['username'])
335 new_user.api_key = generate_api_key(form_data['username'])
315 Session.add(new_user)
336 Session.add(new_user)
316 Session.commit()
337 Session.commit()
317 return new_user
338 return new_user
318 except:
339 except:
319 log.error(traceback.format_exc())
340 log.error(traceback.format_exc())
320 Session.rollback()
341 Session.rollback()
321 raise
342 raise
322
343
323 class UserLog(Base, BaseModel):
344 class UserLog(Base, BaseModel):
324 __tablename__ = 'user_logs'
345 __tablename__ = 'user_logs'
325 __table_args__ = {'extend_existing':True}
346 __table_args__ = {'extend_existing':True}
326 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
347 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
327 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
348 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
328 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
349 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
329 repository_name = Column("repository_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
350 repository_name = Column("repository_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
330 user_ip = Column("user_ip", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
351 user_ip = Column("user_ip", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
331 action = Column("action", UnicodeText(length=1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
352 action = Column("action", UnicodeText(length=1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
332 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
353 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
333
354
334 @property
355 @property
335 def action_as_day(self):
356 def action_as_day(self):
336 return date(*self.action_date.timetuple()[:3])
357 return date(*self.action_date.timetuple()[:3])
337
358
338 user = relationship('User')
359 user = relationship('User')
339 repository = relationship('Repository')
360 repository = relationship('Repository')
340
361
341
362
342 class UsersGroup(Base, BaseModel):
363 class UsersGroup(Base, BaseModel):
343 __tablename__ = 'users_groups'
364 __tablename__ = 'users_groups'
344 __table_args__ = {'extend_existing':True}
365 __table_args__ = {'extend_existing':True}
345
366
346 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
367 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
347 users_group_name = Column("users_group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
368 users_group_name = Column("users_group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
348 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
369 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
349
370
350 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
371 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
351
372
352 def __repr__(self):
373 def __repr__(self):
353 return '<userGroup(%s)>' % (self.users_group_name)
374 return '<userGroup(%s)>' % (self.users_group_name)
354
375
355 @classmethod
376 @classmethod
356 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
377 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
357 if case_insensitive:
378 if case_insensitive:
358 gr = Session.query(cls)\
379 gr = Session.query(cls)\
359 .filter(cls.users_group_name.ilike(group_name))
380 .filter(cls.users_group_name.ilike(group_name))
360 else:
381 else:
361 gr = Session.query(UsersGroup)\
382 gr = Session.query(UsersGroup)\
362 .filter(UsersGroup.users_group_name == group_name)
383 .filter(UsersGroup.users_group_name == group_name)
363 if cache:
384 if cache:
364 gr = gr.options(FromCache("sql_cache_short",
385 gr = gr.options(FromCache("sql_cache_short",
365 "get_user_%s" % group_name))
386 "get_user_%s" % group_name))
366 return gr.scalar()
387 return gr.scalar()
367
388
368
389
369 @classmethod
390 @classmethod
370 def get(cls, users_group_id, cache=False):
391 def get(cls, users_group_id, cache=False):
371 users_group = Session.query(cls)
392 users_group = Session.query(cls)
372 if cache:
393 if cache:
373 users_group = users_group.options(FromCache("sql_cache_short",
394 users_group = users_group.options(FromCache("sql_cache_short",
374 "get_users_group_%s" % users_group_id))
395 "get_users_group_%s" % users_group_id))
375 return users_group.get(users_group_id)
396 return users_group.get(users_group_id)
376
397
377 @classmethod
398 @classmethod
378 def create(cls, form_data):
399 def create(cls, form_data):
379 try:
400 try:
380 new_users_group = cls()
401 new_users_group = cls()
381 for k, v in form_data.items():
402 for k, v in form_data.items():
382 setattr(new_users_group, k, v)
403 setattr(new_users_group, k, v)
383
404
384 Session.add(new_users_group)
405 Session.add(new_users_group)
385 Session.commit()
406 Session.commit()
386 return new_users_group
407 return new_users_group
387 except:
408 except:
388 log.error(traceback.format_exc())
409 log.error(traceback.format_exc())
389 Session.rollback()
410 Session.rollback()
390 raise
411 raise
391
412
392 @classmethod
413 @classmethod
393 def update(cls, users_group_id, form_data):
414 def update(cls, users_group_id, form_data):
394
415
395 try:
416 try:
396 users_group = cls.get(users_group_id, cache=False)
417 users_group = cls.get(users_group_id, cache=False)
397
418
398 for k, v in form_data.items():
419 for k, v in form_data.items():
399 if k == 'users_group_members':
420 if k == 'users_group_members':
400 users_group.members = []
421 users_group.members = []
401 Session.flush()
422 Session.flush()
402 members_list = []
423 members_list = []
403 if v:
424 if v:
404 for u_id in set(v):
425 for u_id in set(v):
405 members_list.append(UsersGroupMember(
426 members_list.append(UsersGroupMember(
406 users_group_id,
427 users_group_id,
407 u_id))
428 u_id))
408 setattr(users_group, 'members', members_list)
429 setattr(users_group, 'members', members_list)
409 setattr(users_group, k, v)
430 setattr(users_group, k, v)
410
431
411 Session.add(users_group)
432 Session.add(users_group)
412 Session.commit()
433 Session.commit()
413 except:
434 except:
414 log.error(traceback.format_exc())
435 log.error(traceback.format_exc())
415 Session.rollback()
436 Session.rollback()
416 raise
437 raise
417
438
418 @classmethod
439 @classmethod
419 def delete(cls, users_group_id):
440 def delete(cls, users_group_id):
420 try:
441 try:
421
442
422 # check if this group is not assigned to repo
443 # check if this group is not assigned to repo
423 assigned_groups = UsersGroupRepoToPerm.query()\
444 assigned_groups = UsersGroupRepoToPerm.query()\
424 .filter(UsersGroupRepoToPerm.users_group_id ==
445 .filter(UsersGroupRepoToPerm.users_group_id ==
425 users_group_id).all()
446 users_group_id).all()
426
447
427 if assigned_groups:
448 if assigned_groups:
428 raise UsersGroupsAssignedException('Group assigned to %s' %
449 raise UsersGroupsAssignedException('Group assigned to %s' %
429 assigned_groups)
450 assigned_groups)
430
451
431 users_group = cls.get(users_group_id, cache=False)
452 users_group = cls.get(users_group_id, cache=False)
432 Session.delete(users_group)
453 Session.delete(users_group)
433 Session.commit()
454 Session.commit()
434 except:
455 except:
435 log.error(traceback.format_exc())
456 log.error(traceback.format_exc())
436 Session.rollback()
457 Session.rollback()
437 raise
458 raise
438
459
439
460
440 class UsersGroupMember(Base, BaseModel):
461 class UsersGroupMember(Base, BaseModel):
441 __tablename__ = 'users_groups_members'
462 __tablename__ = 'users_groups_members'
442 __table_args__ = {'extend_existing':True}
463 __table_args__ = {'extend_existing':True}
443
464
444 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
465 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
445 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
466 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
446 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
467 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
447
468
448 user = relationship('User', lazy='joined')
469 user = relationship('User', lazy='joined')
449 users_group = relationship('UsersGroup')
470 users_group = relationship('UsersGroup')
450
471
451 def __init__(self, gr_id='', u_id=''):
472 def __init__(self, gr_id='', u_id=''):
452 self.users_group_id = gr_id
473 self.users_group_id = gr_id
453 self.user_id = u_id
474 self.user_id = u_id
454
475
455 class Repository(Base, BaseModel):
476 class Repository(Base, BaseModel):
456 __tablename__ = 'repositories'
477 __tablename__ = 'repositories'
457 __table_args__ = (UniqueConstraint('repo_name'), {'extend_existing':True},)
478 __table_args__ = (UniqueConstraint('repo_name'), {'extend_existing':True},)
458
479
459 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
480 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
460 repo_name = Column("repo_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
481 repo_name = Column("repo_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
461 clone_uri = Column("clone_uri", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
482 clone_uri = Column("clone_uri", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
462 repo_type = Column("repo_type", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default='hg')
483 repo_type = Column("repo_type", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default='hg')
463 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
484 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
464 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
485 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
465 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
486 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
466 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
487 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
467 description = Column("description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
488 description = Column("description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
468 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
489 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
469
490
470 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
491 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
471 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
492 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
472
493
473
494
474 user = relationship('User')
495 user = relationship('User')
475 fork = relationship('Repository', remote_side=repo_id)
496 fork = relationship('Repository', remote_side=repo_id)
476 group = relationship('Group')
497 group = relationship('Group')
477 repo_to_perm = relationship('RepoToPerm', cascade='all', order_by='RepoToPerm.repo_to_perm_id')
498 repo_to_perm = relationship('RepoToPerm', cascade='all', order_by='RepoToPerm.repo_to_perm_id')
478 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
499 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
479 stats = relationship('Statistics', cascade='all', uselist=False)
500 stats = relationship('Statistics', cascade='all', uselist=False)
480
501
481 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
502 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
482
503
483 logs = relationship('UserLog', cascade='all')
504 logs = relationship('UserLog', cascade='all')
484
505
485 def __repr__(self):
506 def __repr__(self):
486 return "<%s('%s:%s')>" % (self.__class__.__name__,
507 return "<%s('%s:%s')>" % (self.__class__.__name__,
487 self.repo_id, self.repo_name)
508 self.repo_id, self.repo_name)
488
509
489 @classmethod
510 @classmethod
490 def by_repo_name(cls, repo_name):
511 def url_sep(cls):
512 return '/'
513
514 @classmethod
515 def get_by_repo_name(cls, repo_name):
491 q = Session.query(cls).filter(cls.repo_name == repo_name)
516 q = Session.query(cls).filter(cls.repo_name == repo_name)
492
517
493 q = q.options(joinedload(Repository.fork))\
518 q = q.options(joinedload(Repository.fork))\
494 .options(joinedload(Repository.user))\
519 .options(joinedload(Repository.user))\
495 .options(joinedload(Repository.group))\
520 .options(joinedload(Repository.group))\
496
521
497 return q.one()
522 return q.one()
498
523
499 @classmethod
524 @classmethod
500 def get_repo_forks(cls, repo_id):
525 def get_repo_forks(cls, repo_id):
501 return Session.query(cls).filter(Repository.fork_id == repo_id)
526 return Session.query(cls).filter(Repository.fork_id == repo_id)
502
527
503 @classmethod
528 @classmethod
504 def base_path(cls):
529 def base_path(cls):
505 """
530 """
506 Returns base path when all repos are stored
531 Returns base path when all repos are stored
507
532
508 :param cls:
533 :param cls:
509 """
534 """
510 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/')
535 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
536 cls.url_sep())
511 q.options(FromCache("sql_cache_short", "repository_repo_path"))
537 q.options(FromCache("sql_cache_short", "repository_repo_path"))
512 return q.one().ui_value
538 return q.one().ui_value
513
539
514 @property
540 @property
515 def just_name(self):
541 def just_name(self):
516 return self.repo_name.split(os.sep)[-1]
542 return self.repo_name.split(Repository.url_sep())[-1]
517
543
518 @property
544 @property
519 def groups_with_parents(self):
545 def groups_with_parents(self):
520 groups = []
546 groups = []
521 if self.group is None:
547 if self.group is None:
522 return groups
548 return groups
523
549
524 cur_gr = self.group
550 cur_gr = self.group
525 groups.insert(0, cur_gr)
551 groups.insert(0, cur_gr)
526 while 1:
552 while 1:
527 gr = getattr(cur_gr, 'parent_group', None)
553 gr = getattr(cur_gr, 'parent_group', None)
528 cur_gr = cur_gr.parent_group
554 cur_gr = cur_gr.parent_group
529 if gr is None:
555 if gr is None:
530 break
556 break
531 groups.insert(0, gr)
557 groups.insert(0, gr)
532
558
533 return groups
559 return groups
534
560
535 @property
561 @property
536 def groups_and_repo(self):
562 def groups_and_repo(self):
537 return self.groups_with_parents, self.just_name
563 return self.groups_with_parents, self.just_name
538
564
539 @LazyProperty
565 @LazyProperty
540 def repo_path(self):
566 def repo_path(self):
541 """
567 """
542 Returns base full path for that repository means where it actually
568 Returns base full path for that repository means where it actually
543 exists on a filesystem
569 exists on a filesystem
544 """
570 """
545 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/')
571 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
572 Repository.url_sep())
546 q.options(FromCache("sql_cache_short", "repository_repo_path"))
573 q.options(FromCache("sql_cache_short", "repository_repo_path"))
547 return q.one().ui_value
574 return q.one().ui_value
548
575
549 @property
576 @property
550 def repo_full_path(self):
577 def repo_full_path(self):
551 p = [self.repo_path]
578 p = [self.repo_path]
552 # we need to split the name by / since this is how we store the
579 # we need to split the name by / since this is how we store the
553 # names in the database, but that eventually needs to be converted
580 # names in the database, but that eventually needs to be converted
554 # into a valid system path
581 # into a valid system path
555 p += self.repo_name.split('/')
582 p += self.repo_name.split(Repository.url_sep())
556 return os.path.join(*p)
583 return os.path.join(*p)
557
584
585 def get_new_name(self, repo_name):
586 """
587 returns new full repository name based on assigned group and new new
588
589 :param group_name:
590 """
591 path_prefix = self.group.full_path_splitted if self.group else []
592 return Repository.url_sep().join(path_prefix + [repo_name])
593
558 @property
594 @property
559 def _ui(self):
595 def _ui(self):
560 """
596 """
561 Creates an db based ui object for this repository
597 Creates an db based ui object for this repository
562 """
598 """
563 from mercurial import ui
599 from mercurial import ui
564 from mercurial import config
600 from mercurial import config
565 baseui = ui.ui()
601 baseui = ui.ui()
566
602
567 #clean the baseui object
603 #clean the baseui object
568 baseui._ocfg = config.config()
604 baseui._ocfg = config.config()
569 baseui._ucfg = config.config()
605 baseui._ucfg = config.config()
570 baseui._tcfg = config.config()
606 baseui._tcfg = config.config()
571
607
572
608
573 ret = Session.query(RhodeCodeUi)\
609 ret = Session.query(RhodeCodeUi)\
574 .options(FromCache("sql_cache_short", "repository_repo_ui")).all()
610 .options(FromCache("sql_cache_short", "repository_repo_ui")).all()
575
611
576 hg_ui = ret
612 hg_ui = ret
577 for ui_ in hg_ui:
613 for ui_ in hg_ui:
578 if ui_.ui_active:
614 if ui_.ui_active:
579 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
615 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
580 ui_.ui_key, ui_.ui_value)
616 ui_.ui_key, ui_.ui_value)
581 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
617 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
582
618
583 return baseui
619 return baseui
584
620
585 @classmethod
621 @classmethod
586 def is_valid(cls, repo_name):
622 def is_valid(cls, repo_name):
587 """
623 """
588 returns True if given repo name is a valid filesystem repository
624 returns True if given repo name is a valid filesystem repository
589
625
590 @param cls:
626 @param cls:
591 @param repo_name:
627 @param repo_name:
592 """
628 """
593 from rhodecode.lib.utils import is_valid_repo
629 from rhodecode.lib.utils import is_valid_repo
594
630
595 return is_valid_repo(repo_name, cls.base_path())
631 return is_valid_repo(repo_name, cls.base_path())
596
632
597
633
598 #==========================================================================
634 #==========================================================================
599 # SCM PROPERTIES
635 # SCM PROPERTIES
600 #==========================================================================
636 #==========================================================================
601
637
602 def get_changeset(self, rev):
638 def get_changeset(self, rev):
603 return get_changeset_safe(self.scm_instance, rev)
639 return get_changeset_safe(self.scm_instance, rev)
604
640
605 @property
641 @property
606 def tip(self):
642 def tip(self):
607 return self.get_changeset('tip')
643 return self.get_changeset('tip')
608
644
609 @property
645 @property
610 def author(self):
646 def author(self):
611 return self.tip.author
647 return self.tip.author
612
648
613 @property
649 @property
614 def last_change(self):
650 def last_change(self):
615 return self.scm_instance.last_change
651 return self.scm_instance.last_change
616
652
617 #==========================================================================
653 #==========================================================================
618 # SCM CACHE INSTANCE
654 # SCM CACHE INSTANCE
619 #==========================================================================
655 #==========================================================================
620
656
621 @property
657 @property
622 def invalidate(self):
658 def invalidate(self):
623 """
659 """
624 Returns Invalidation object if this repo should be invalidated
660 Returns Invalidation object if this repo should be invalidated
625 None otherwise. `cache_active = False` means that this cache
661 None otherwise. `cache_active = False` means that this cache
626 state is not valid and needs to be invalidated
662 state is not valid and needs to be invalidated
627 """
663 """
628 return Session.query(CacheInvalidation)\
664 return Session.query(CacheInvalidation)\
629 .filter(CacheInvalidation.cache_key == self.repo_name)\
665 .filter(CacheInvalidation.cache_key == self.repo_name)\
630 .filter(CacheInvalidation.cache_active == False)\
666 .filter(CacheInvalidation.cache_active == False)\
631 .scalar()
667 .scalar()
632
668
633 def set_invalidate(self):
669 def set_invalidate(self):
634 """
670 """
635 set a cache for invalidation for this instance
671 set a cache for invalidation for this instance
636 """
672 """
637 inv = Session.query(CacheInvalidation)\
673 inv = Session.query(CacheInvalidation)\
638 .filter(CacheInvalidation.cache_key == self.repo_name)\
674 .filter(CacheInvalidation.cache_key == self.repo_name)\
639 .scalar()
675 .scalar()
640
676
641 if inv is None:
677 if inv is None:
642 inv = CacheInvalidation(self.repo_name)
678 inv = CacheInvalidation(self.repo_name)
643 inv.cache_active = True
679 inv.cache_active = True
644 Session.add(inv)
680 Session.add(inv)
645 Session.commit()
681 Session.commit()
646
682
647 @LazyProperty
683 @LazyProperty
648 def scm_instance(self):
684 def scm_instance(self):
649 return self.__get_instance()
685 return self.__get_instance()
650
686
651 @property
687 @property
652 def scm_instance_cached(self):
688 def scm_instance_cached(self):
653 @cache_region('long_term')
689 @cache_region('long_term')
654 def _c(repo_name):
690 def _c(repo_name):
655 return self.__get_instance()
691 return self.__get_instance()
656
692
657 # TODO: remove this trick when beaker 1.6 is released
693 # TODO: remove this trick when beaker 1.6 is released
658 # and have fixed this issue with not supporting unicode keys
694 # and have fixed this issue with not supporting unicode keys
659 rn = safe_str(self.repo_name)
695 rn = safe_str(self.repo_name)
660
696
661 inv = self.invalidate
697 inv = self.invalidate
662 if inv is not None:
698 if inv is not None:
663 region_invalidate(_c, None, rn)
699 region_invalidate(_c, None, rn)
664 # update our cache
700 # update our cache
665 inv.cache_active = True
701 inv.cache_active = True
666 Session.add(inv)
702 Session.add(inv)
667 Session.commit()
703 Session.commit()
668
704
669 return _c(rn)
705 return _c(rn)
670
706
671 def __get_instance(self):
707 def __get_instance(self):
672
708
673 repo_full_path = self.repo_full_path
709 repo_full_path = self.repo_full_path
674
710
675 try:
711 try:
676 alias = get_scm(repo_full_path)[0]
712 alias = get_scm(repo_full_path)[0]
677 log.debug('Creating instance of %s repository', alias)
713 log.debug('Creating instance of %s repository', alias)
678 backend = get_backend(alias)
714 backend = get_backend(alias)
679 except VCSError:
715 except VCSError:
680 log.error(traceback.format_exc())
716 log.error(traceback.format_exc())
681 log.error('Perhaps this repository is in db and not in '
717 log.error('Perhaps this repository is in db and not in '
682 'filesystem run rescan repositories with '
718 'filesystem run rescan repositories with '
683 '"destroy old data " option from admin panel')
719 '"destroy old data " option from admin panel')
684 return
720 return
685
721
686 if alias == 'hg':
722 if alias == 'hg':
687
723
688 repo = backend(safe_str(repo_full_path), create=False,
724 repo = backend(safe_str(repo_full_path), create=False,
689 baseui=self._ui)
725 baseui=self._ui)
690 #skip hidden web repository
726 #skip hidden web repository
691 if repo._get_hidden():
727 if repo._get_hidden():
692 return
728 return
693 else:
729 else:
694 repo = backend(repo_full_path, create=False)
730 repo = backend(repo_full_path, create=False)
695
731
696 return repo
732 return repo
697
733
698
734
699 class Group(Base, BaseModel):
735 class Group(Base, BaseModel):
700 __tablename__ = 'groups'
736 __tablename__ = 'groups'
701 __table_args__ = (UniqueConstraint('group_name', 'group_parent_id'),
737 __table_args__ = (UniqueConstraint('group_name', 'group_parent_id'),
702 CheckConstraint('group_id != group_parent_id'), {'extend_existing':True},)
738 CheckConstraint('group_id != group_parent_id'), {'extend_existing':True},)
703 __mapper_args__ = {'order_by':'group_name'}
739 __mapper_args__ = {'order_by':'group_name'}
704
740
705 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
741 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
706 group_name = Column("group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
742 group_name = Column("group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
707 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
743 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
708 group_description = Column("group_description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
744 group_description = Column("group_description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
709
745
710 parent_group = relationship('Group', remote_side=group_id)
746 parent_group = relationship('Group', remote_side=group_id)
711
747
712
748
713 def __init__(self, group_name='', parent_group=None):
749 def __init__(self, group_name='', parent_group=None):
714 self.group_name = group_name
750 self.group_name = group_name
715 self.parent_group = parent_group
751 self.parent_group = parent_group
716
752
717 def __repr__(self):
753 def __repr__(self):
718 return "<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
754 return "<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
719 self.group_name)
755 self.group_name)
720
756
721 @classmethod
757 @classmethod
758 def groups_choices(cls):
759 from webhelpers.html import literal as _literal
760 repo_groups = [('', '')]
761 sep = ' &raquo; '
762 _name = lambda k: _literal(sep.join(k))
763
764 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
765 for x in cls.query().all()])
766
767 repo_groups = sorted(repo_groups,key=lambda t: t[1].split(sep)[0])
768 return repo_groups
769
770 @classmethod
722 def url_sep(cls):
771 def url_sep(cls):
723 return '/'
772 return '/'
724
773
774 @classmethod
775 def get_by_group_name(cls, group_name):
776 return cls.query().filter(cls.group_name == group_name).scalar()
777
725 @property
778 @property
726 def parents(self):
779 def parents(self):
727 parents_recursion_limit = 5
780 parents_recursion_limit = 5
728 groups = []
781 groups = []
729 if self.parent_group is None:
782 if self.parent_group is None:
730 return groups
783 return groups
731 cur_gr = self.parent_group
784 cur_gr = self.parent_group
732 groups.insert(0, cur_gr)
785 groups.insert(0, cur_gr)
733 cnt = 0
786 cnt = 0
734 while 1:
787 while 1:
735 cnt += 1
788 cnt += 1
736 gr = getattr(cur_gr, 'parent_group', None)
789 gr = getattr(cur_gr, 'parent_group', None)
737 cur_gr = cur_gr.parent_group
790 cur_gr = cur_gr.parent_group
738 if gr is None:
791 if gr is None:
739 break
792 break
740 if cnt == parents_recursion_limit:
793 if cnt == parents_recursion_limit:
741 # this will prevent accidental infinit loops
794 # this will prevent accidental infinit loops
742 log.error('group nested more than %s' %
795 log.error('group nested more than %s' %
743 parents_recursion_limit)
796 parents_recursion_limit)
744 break
797 break
745
798
746 groups.insert(0, gr)
799 groups.insert(0, gr)
747 return groups
800 return groups
748
801
749 @property
802 @property
750 def children(self):
803 def children(self):
751 return Session.query(Group).filter(Group.parent_group == self)
804 return Session.query(Group).filter(Group.parent_group == self)
752
805
753 @property
806 @property
807 def name(self):
808 return self.group_name.split(Group.url_sep())[-1]
809
810 @property
754 def full_path(self):
811 def full_path(self):
755 return Group.url_sep().join([g.group_name for g in self.parents] +
812 return self.group_name
756 [self.group_name])
813
814 @property
815 def full_path_splitted(self):
816 return self.group_name.split(Group.url_sep())
757
817
758 @property
818 @property
759 def repositories(self):
819 def repositories(self):
760 return Session.query(Repository).filter(Repository.group == self)
820 return Session.query(Repository).filter(Repository.group == self)
761
821
762 @property
822 @property
763 def repositories_recursive_count(self):
823 def repositories_recursive_count(self):
764 cnt = self.repositories.count()
824 cnt = self.repositories.count()
765
825
766 def children_count(group):
826 def children_count(group):
767 cnt = 0
827 cnt = 0
768 for child in group.children:
828 for child in group.children:
769 cnt += child.repositories.count()
829 cnt += child.repositories.count()
770 cnt += children_count(child)
830 cnt += children_count(child)
771 return cnt
831 return cnt
772
832
773 return cnt + children_count(self)
833 return cnt + children_count(self)
774
834
835
836 def get_new_name(self, group_name):
837 """
838 returns new full group name based on parent and new name
839
840 :param group_name:
841 """
842 path_prefix = self.parent_group.full_path_splitted if self.parent_group else []
843 return Group.url_sep().join(path_prefix + [group_name])
844
845
775 class Permission(Base, BaseModel):
846 class Permission(Base, BaseModel):
776 __tablename__ = 'permissions'
847 __tablename__ = 'permissions'
777 __table_args__ = {'extend_existing':True}
848 __table_args__ = {'extend_existing':True}
778 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
849 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
779 permission_name = Column("permission_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
850 permission_name = Column("permission_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
780 permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
851 permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
781
852
782 def __repr__(self):
853 def __repr__(self):
783 return "<%s('%s:%s')>" % (self.__class__.__name__,
854 return "<%s('%s:%s')>" % (self.__class__.__name__,
784 self.permission_id, self.permission_name)
855 self.permission_id, self.permission_name)
785
856
786 @classmethod
857 @classmethod
787 def get_by_key(cls, key):
858 def get_by_key(cls, key):
788 return Session.query(cls).filter(cls.permission_name == key).scalar()
859 return Session.query(cls).filter(cls.permission_name == key).scalar()
789
860
790 class RepoToPerm(Base, BaseModel):
861 class RepoToPerm(Base, BaseModel):
791 __tablename__ = 'repo_to_perm'
862 __tablename__ = 'repo_to_perm'
792 __table_args__ = (UniqueConstraint('user_id', 'repository_id'), {'extend_existing':True})
863 __table_args__ = (UniqueConstraint('user_id', 'repository_id'), {'extend_existing':True})
793 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
864 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
794 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
865 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
795 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
866 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
796 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
867 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
797
868
798 user = relationship('User')
869 user = relationship('User')
799 permission = relationship('Permission')
870 permission = relationship('Permission')
800 repository = relationship('Repository')
871 repository = relationship('Repository')
801
872
802 class UserToPerm(Base, BaseModel):
873 class UserToPerm(Base, BaseModel):
803 __tablename__ = 'user_to_perm'
874 __tablename__ = 'user_to_perm'
804 __table_args__ = (UniqueConstraint('user_id', 'permission_id'), {'extend_existing':True})
875 __table_args__ = (UniqueConstraint('user_id', 'permission_id'), {'extend_existing':True})
805 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
876 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
806 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
877 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
807 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
878 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
808
879
809 user = relationship('User')
880 user = relationship('User')
810 permission = relationship('Permission')
881 permission = relationship('Permission')
811
882
812 @classmethod
883 @classmethod
813 def has_perm(cls, user_id, perm):
884 def has_perm(cls, user_id, perm):
814 if not isinstance(perm, Permission):
885 if not isinstance(perm, Permission):
815 raise Exception('perm needs to be an instance of Permission class')
886 raise Exception('perm needs to be an instance of Permission class')
816
887
817 return Session.query(cls).filter(cls.user_id == user_id)\
888 return Session.query(cls).filter(cls.user_id == user_id)\
818 .filter(cls.permission == perm).scalar() is not None
889 .filter(cls.permission == perm).scalar() is not None
819
890
820 @classmethod
891 @classmethod
821 def grant_perm(cls, user_id, perm):
892 def grant_perm(cls, user_id, perm):
822 if not isinstance(perm, Permission):
893 if not isinstance(perm, Permission):
823 raise Exception('perm needs to be an instance of Permission class')
894 raise Exception('perm needs to be an instance of Permission class')
824
895
825 new = cls()
896 new = cls()
826 new.user_id = user_id
897 new.user_id = user_id
827 new.permission = perm
898 new.permission = perm
828 try:
899 try:
829 Session.add(new)
900 Session.add(new)
830 Session.commit()
901 Session.commit()
831 except:
902 except:
832 Session.rollback()
903 Session.rollback()
833
904
834
905
835 @classmethod
906 @classmethod
836 def revoke_perm(cls, user_id, perm):
907 def revoke_perm(cls, user_id, perm):
837 if not isinstance(perm, Permission):
908 if not isinstance(perm, Permission):
838 raise Exception('perm needs to be an instance of Permission class')
909 raise Exception('perm needs to be an instance of Permission class')
839
910
840 try:
911 try:
841 Session.query(cls).filter(cls.user_id == user_id)\
912 Session.query(cls).filter(cls.user_id == user_id)\
842 .filter(cls.permission == perm).delete()
913 .filter(cls.permission == perm).delete()
843 Session.commit()
914 Session.commit()
844 except:
915 except:
845 Session.rollback()
916 Session.rollback()
846
917
847 class UsersGroupRepoToPerm(Base, BaseModel):
918 class UsersGroupRepoToPerm(Base, BaseModel):
848 __tablename__ = 'users_group_repo_to_perm'
919 __tablename__ = 'users_group_repo_to_perm'
849 __table_args__ = (UniqueConstraint('repository_id', 'users_group_id', 'permission_id'), {'extend_existing':True})
920 __table_args__ = (UniqueConstraint('repository_id', 'users_group_id', 'permission_id'), {'extend_existing':True})
850 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
921 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
851 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
922 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
852 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
923 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
853 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
924 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
854
925
855 users_group = relationship('UsersGroup')
926 users_group = relationship('UsersGroup')
856 permission = relationship('Permission')
927 permission = relationship('Permission')
857 repository = relationship('Repository')
928 repository = relationship('Repository')
858
929
859 def __repr__(self):
930 def __repr__(self):
860 return '<userGroup:%s => %s >' % (self.users_group, self.repository)
931 return '<userGroup:%s => %s >' % (self.users_group, self.repository)
861
932
862 class UsersGroupToPerm(Base, BaseModel):
933 class UsersGroupToPerm(Base, BaseModel):
863 __tablename__ = 'users_group_to_perm'
934 __tablename__ = 'users_group_to_perm'
864 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
935 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
865 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
936 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
866 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
937 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
867
938
868 users_group = relationship('UsersGroup')
939 users_group = relationship('UsersGroup')
869 permission = relationship('Permission')
940 permission = relationship('Permission')
870
941
871
942
872 @classmethod
943 @classmethod
873 def has_perm(cls, users_group_id, perm):
944 def has_perm(cls, users_group_id, perm):
874 if not isinstance(perm, Permission):
945 if not isinstance(perm, Permission):
875 raise Exception('perm needs to be an instance of Permission class')
946 raise Exception('perm needs to be an instance of Permission class')
876
947
877 return Session.query(cls).filter(cls.users_group_id ==
948 return Session.query(cls).filter(cls.users_group_id ==
878 users_group_id)\
949 users_group_id)\
879 .filter(cls.permission == perm)\
950 .filter(cls.permission == perm)\
880 .scalar() is not None
951 .scalar() is not None
881
952
882 @classmethod
953 @classmethod
883 def grant_perm(cls, users_group_id, perm):
954 def grant_perm(cls, users_group_id, perm):
884 if not isinstance(perm, Permission):
955 if not isinstance(perm, Permission):
885 raise Exception('perm needs to be an instance of Permission class')
956 raise Exception('perm needs to be an instance of Permission class')
886
957
887 new = cls()
958 new = cls()
888 new.users_group_id = users_group_id
959 new.users_group_id = users_group_id
889 new.permission = perm
960 new.permission = perm
890 try:
961 try:
891 Session.add(new)
962 Session.add(new)
892 Session.commit()
963 Session.commit()
893 except:
964 except:
894 Session.rollback()
965 Session.rollback()
895
966
896
967
897 @classmethod
968 @classmethod
898 def revoke_perm(cls, users_group_id, perm):
969 def revoke_perm(cls, users_group_id, perm):
899 if not isinstance(perm, Permission):
970 if not isinstance(perm, Permission):
900 raise Exception('perm needs to be an instance of Permission class')
971 raise Exception('perm needs to be an instance of Permission class')
901
972
902 try:
973 try:
903 Session.query(cls).filter(cls.users_group_id == users_group_id)\
974 Session.query(cls).filter(cls.users_group_id == users_group_id)\
904 .filter(cls.permission == perm).delete()
975 .filter(cls.permission == perm).delete()
905 Session.commit()
976 Session.commit()
906 except:
977 except:
907 Session.rollback()
978 Session.rollback()
908
979
909
980
910 class GroupToPerm(Base, BaseModel):
981 class GroupToPerm(Base, BaseModel):
911 __tablename__ = 'group_to_perm'
982 __tablename__ = 'group_to_perm'
912 __table_args__ = (UniqueConstraint('group_id', 'permission_id'), {'extend_existing':True})
983 __table_args__ = (UniqueConstraint('group_id', 'permission_id'), {'extend_existing':True})
913
984
914 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
985 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
915 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
986 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
916 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
987 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
917 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
988 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
918
989
919 user = relationship('User')
990 user = relationship('User')
920 permission = relationship('Permission')
991 permission = relationship('Permission')
921 group = relationship('Group')
992 group = relationship('Group')
922
993
923 class Statistics(Base, BaseModel):
994 class Statistics(Base, BaseModel):
924 __tablename__ = 'statistics'
995 __tablename__ = 'statistics'
925 __table_args__ = (UniqueConstraint('repository_id'), {'extend_existing':True})
996 __table_args__ = (UniqueConstraint('repository_id'), {'extend_existing':True})
926 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
997 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
927 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
998 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
928 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
999 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
929 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1000 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
930 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1001 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
931 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1002 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
932
1003
933 repository = relationship('Repository', single_parent=True)
1004 repository = relationship('Repository', single_parent=True)
934
1005
935 class UserFollowing(Base, BaseModel):
1006 class UserFollowing(Base, BaseModel):
936 __tablename__ = 'user_followings'
1007 __tablename__ = 'user_followings'
937 __table_args__ = (UniqueConstraint('user_id', 'follows_repository_id'),
1008 __table_args__ = (UniqueConstraint('user_id', 'follows_repository_id'),
938 UniqueConstraint('user_id', 'follows_user_id')
1009 UniqueConstraint('user_id', 'follows_user_id')
939 , {'extend_existing':True})
1010 , {'extend_existing':True})
940
1011
941 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1012 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
942 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1013 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
943 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1014 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
944 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1015 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
945 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1016 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
946
1017
947 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1018 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
948
1019
949 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1020 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
950 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1021 follows_repository = relationship('Repository', order_by='Repository.repo_name')
951
1022
952
1023
953 @classmethod
1024 @classmethod
954 def get_repo_followers(cls, repo_id):
1025 def get_repo_followers(cls, repo_id):
955 return Session.query(cls).filter(cls.follows_repo_id == repo_id)
1026 return Session.query(cls).filter(cls.follows_repo_id == repo_id)
956
1027
957 class CacheInvalidation(Base, BaseModel):
1028 class CacheInvalidation(Base, BaseModel):
958 __tablename__ = 'cache_invalidation'
1029 __tablename__ = 'cache_invalidation'
959 __table_args__ = (UniqueConstraint('cache_key'), {'extend_existing':True})
1030 __table_args__ = (UniqueConstraint('cache_key'), {'extend_existing':True})
960 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1031 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
961 cache_key = Column("cache_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1032 cache_key = Column("cache_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
962 cache_args = Column("cache_args", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1033 cache_args = Column("cache_args", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
963 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1034 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
964
1035
965
1036
966 def __init__(self, cache_key, cache_args=''):
1037 def __init__(self, cache_key, cache_args=''):
967 self.cache_key = cache_key
1038 self.cache_key = cache_key
968 self.cache_args = cache_args
1039 self.cache_args = cache_args
969 self.cache_active = False
1040 self.cache_active = False
970
1041
971 def __repr__(self):
1042 def __repr__(self):
972 return "<%s('%s:%s')>" % (self.__class__.__name__,
1043 return "<%s('%s:%s')>" % (self.__class__.__name__,
973 self.cache_id, self.cache_key)
1044 self.cache_id, self.cache_key)
974
1045
975 class DbMigrateVersion(Base, BaseModel):
1046 class DbMigrateVersion(Base, BaseModel):
976 __tablename__ = 'db_migrate_version'
1047 __tablename__ = 'db_migrate_version'
977 __table_args__ = {'extend_existing':True}
1048 __table_args__ = {'extend_existing':True}
978 repository_id = Column('repository_id', String(250), primary_key=True)
1049 repository_id = Column('repository_id', String(250), primary_key=True)
979 repository_path = Column('repository_path', Text)
1050 repository_path = Column('repository_path', Text)
980 version = Column('version', Integer)
1051 version = Column('version', Integer)
@@ -1,681 +1,688
1 """ this is forms validation classes
1 """ this is forms validation classes
2 http://formencode.org/module-formencode.validators.html
2 http://formencode.org/module-formencode.validators.html
3 for list off all availible validators
3 for list off all availible validators
4
4
5 we can create our own validators
5 we can create our own validators
6
6
7 The table below outlines the options which can be used in a schema in addition to the validators themselves
7 The table below outlines the options which can be used in a schema in addition to the validators themselves
8 pre_validators [] These validators will be applied before the schema
8 pre_validators [] These validators will be applied before the schema
9 chained_validators [] These validators will be applied after the schema
9 chained_validators [] These validators will be applied after the schema
10 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
10 allow_extra_fields False If True, then it is not an error when keys that aren't associated with a validator are present
11 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
11 filter_extra_fields False If True, then keys that aren't associated with a validator are removed
12 if_key_missing NoDefault If this is given, then any keys that aren't available but are expected will be replaced with this value (and then validated). This does not override a present .if_missing attribute on validators. NoDefault is a special FormEncode class to mean that no default values has been specified and therefore missing keys shouldn't take a default value.
12 if_key_missing NoDefault If this is given, then any keys that aren't available but are expected will be replaced with this value (and then validated). This does not override a present .if_missing attribute on validators. NoDefault is a special FormEncode class to mean that no default values has been specified and therefore missing keys shouldn't take a default value.
13 ignore_key_missing False If True, then missing keys will be missing in the result, if the validator doesn't have .if_missing on it already
13 ignore_key_missing False If True, then missing keys will be missing in the result, if the validator doesn't have .if_missing on it already
14
14
15
15
16 <name> = formencode.validators.<name of validator>
16 <name> = formencode.validators.<name of validator>
17 <name> must equal form name
17 <name> must equal form name
18 list=[1,2,3,4,5]
18 list=[1,2,3,4,5]
19 for SELECT use formencode.All(OneOf(list), Int())
19 for SELECT use formencode.All(OneOf(list), Int())
20
20
21 """
21 """
22 import os
22 import os
23 import re
23 import re
24 import logging
24 import logging
25 import traceback
25 import traceback
26
26
27 import formencode
27 import formencode
28 from formencode import All
28 from formencode import All
29 from formencode.validators import UnicodeString, OneOf, Int, Number, Regex, \
29 from formencode.validators import UnicodeString, OneOf, Int, Number, Regex, \
30 Email, Bool, StringBoolean, Set
30 Email, Bool, StringBoolean, Set
31
31
32 from pylons.i18n.translation import _
32 from pylons.i18n.translation import _
33 from webhelpers.pylonslib.secure_form import authentication_token
33 from webhelpers.pylonslib.secure_form import authentication_token
34
34
35 from rhodecode.config.routing import ADMIN_PREFIX
35 from rhodecode.lib.utils import repo_name_slug
36 from rhodecode.lib.utils import repo_name_slug
36 from rhodecode.lib.auth import authenticate, get_crypt_password
37 from rhodecode.lib.auth import authenticate, get_crypt_password
37 from rhodecode.lib.exceptions import LdapImportError
38 from rhodecode.lib.exceptions import LdapImportError
38 from rhodecode.model.user import UserModel
39 from rhodecode.model.user import UserModel
39 from rhodecode.model.repo import RepoModel
40 from rhodecode.model.repo import RepoModel
40 from rhodecode.model.db import User, UsersGroup, Group
41 from rhodecode.model.db import User, UsersGroup, Group
41 from rhodecode import BACKENDS
42 from rhodecode import BACKENDS
42
43
43 log = logging.getLogger(__name__)
44 log = logging.getLogger(__name__)
44
45
45 #this is needed to translate the messages using _() in validators
46 #this is needed to translate the messages using _() in validators
46 class State_obj(object):
47 class State_obj(object):
47 _ = staticmethod(_)
48 _ = staticmethod(_)
48
49
49 #==============================================================================
50 #==============================================================================
50 # VALIDATORS
51 # VALIDATORS
51 #==============================================================================
52 #==============================================================================
52 class ValidAuthToken(formencode.validators.FancyValidator):
53 class ValidAuthToken(formencode.validators.FancyValidator):
53 messages = {'invalid_token':_('Token mismatch')}
54 messages = {'invalid_token':_('Token mismatch')}
54
55
55 def validate_python(self, value, state):
56 def validate_python(self, value, state):
56
57
57 if value != authentication_token():
58 if value != authentication_token():
58 raise formencode.Invalid(self.message('invalid_token', state,
59 raise formencode.Invalid(self.message('invalid_token', state,
59 search_number=value), value, state)
60 search_number=value), value, state)
60
61
61 def ValidUsername(edit, old_data):
62 def ValidUsername(edit, old_data):
62 class _ValidUsername(formencode.validators.FancyValidator):
63 class _ValidUsername(formencode.validators.FancyValidator):
63
64
64 def validate_python(self, value, state):
65 def validate_python(self, value, state):
65 if value in ['default', 'new_user']:
66 if value in ['default', 'new_user']:
66 raise formencode.Invalid(_('Invalid username'), value, state)
67 raise formencode.Invalid(_('Invalid username'), value, state)
67 #check if user is unique
68 #check if user is unique
68 old_un = None
69 old_un = None
69 if edit:
70 if edit:
70 old_un = UserModel().get(old_data.get('user_id')).username
71 old_un = UserModel().get(old_data.get('user_id')).username
71
72
72 if old_un != value or not edit:
73 if old_un != value or not edit:
73 if UserModel().get_by_username(value, cache=False,
74 if User.get_by_username(value, case_insensitive=True):
74 case_insensitive=True):
75 raise formencode.Invalid(_('This username already '
75 raise formencode.Invalid(_('This username already '
76 'exists') , value, state)
76 'exists') , value, state)
77
77
78 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
78 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
79 raise formencode.Invalid(_('Username may only contain '
79 raise formencode.Invalid(_('Username may only contain '
80 'alphanumeric characters '
80 'alphanumeric characters '
81 'underscores, periods or dashes '
81 'underscores, periods or dashes '
82 'and must begin with alphanumeric '
82 'and must begin with alphanumeric '
83 'character'), value, state)
83 'character'), value, state)
84
84
85 return _ValidUsername
85 return _ValidUsername
86
86
87
87
88 def ValidUsersGroup(edit, old_data):
88 def ValidUsersGroup(edit, old_data):
89
89
90 class _ValidUsersGroup(formencode.validators.FancyValidator):
90 class _ValidUsersGroup(formencode.validators.FancyValidator):
91
91
92 def validate_python(self, value, state):
92 def validate_python(self, value, state):
93 if value in ['default']:
93 if value in ['default']:
94 raise formencode.Invalid(_('Invalid group name'), value, state)
94 raise formencode.Invalid(_('Invalid group name'), value, state)
95 #check if group is unique
95 #check if group is unique
96 old_ugname = None
96 old_ugname = None
97 if edit:
97 if edit:
98 old_ugname = UsersGroup.get(
98 old_ugname = UsersGroup.get(
99 old_data.get('users_group_id')).users_group_name
99 old_data.get('users_group_id')).users_group_name
100
100
101 if old_ugname != value or not edit:
101 if old_ugname != value or not edit:
102 if UsersGroup.get_by_group_name(value, cache=False,
102 if UsersGroup.get_by_group_name(value, cache=False,
103 case_insensitive=True):
103 case_insensitive=True):
104 raise formencode.Invalid(_('This users group '
104 raise formencode.Invalid(_('This users group '
105 'already exists') , value,
105 'already exists') , value,
106 state)
106 state)
107
107
108
108
109 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
109 if re.match(r'^[a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+$', value) is None:
110 raise formencode.Invalid(_('Group name may only contain '
110 raise formencode.Invalid(_('Group name may only contain '
111 'alphanumeric characters '
111 'alphanumeric characters '
112 'underscores, periods or dashes '
112 'underscores, periods or dashes '
113 'and must begin with alphanumeric '
113 'and must begin with alphanumeric '
114 'character'), value, state)
114 'character'), value, state)
115
115
116 return _ValidUsersGroup
116 return _ValidUsersGroup
117
117
118
118
119 def ValidReposGroup(edit, old_data):
119 def ValidReposGroup(edit, old_data):
120 class _ValidReposGroup(formencode.validators.FancyValidator):
120 class _ValidReposGroup(formencode.validators.FancyValidator):
121
121
122 def validate_python(self, value, state):
122 def validate_python(self, value, state):
123 #TODO WRITE VALIDATIONS
123 #TODO WRITE VALIDATIONS
124 group_name = value.get('group_name')
124 group_name = value.get('group_name')
125 group_parent_id = int(value.get('group_parent_id') or - 1)
125 group_parent_id = int(value.get('group_parent_id') or -1)
126
126
127 # slugify repo group just in case :)
127 # slugify repo group just in case :)
128 slug = repo_name_slug(group_name)
128 slug = repo_name_slug(group_name)
129
129
130 # check for parent of self
130 # check for parent of self
131 if edit and old_data['group_id'] == group_parent_id:
131 if edit and old_data['group_id'] == group_parent_id:
132 e_dict = {'group_parent_id':_('Cannot assign this group '
132 e_dict = {'group_parent_id':_('Cannot assign this group '
133 'as parent')}
133 'as parent')}
134 raise formencode.Invalid('', value, state,
134 raise formencode.Invalid('', value, state,
135 error_dict=e_dict)
135 error_dict=e_dict)
136
136
137 old_gname = None
137 old_gname = None
138 if edit:
138 if edit:
139 old_gname = Group.get(
139 old_gname = Group.get(
140 old_data.get('group_id')).group_name
140 old_data.get('group_id')).group_name
141
141
142 if old_gname != group_name or not edit:
142 if old_gname != group_name or not edit:
143 # check filesystem
143 # check filesystem
144 gr = Group.query().filter(Group.group_name == slug)\
144 gr = Group.query().filter(Group.group_name == slug)\
145 .filter(Group.group_parent_id == group_parent_id).scalar()
145 .filter(Group.group_parent_id == group_parent_id).scalar()
146
146
147 if gr:
147 if gr:
148 e_dict = {'group_name':_('This group already exists')}
148 e_dict = {'group_name':_('This group already exists')}
149 raise formencode.Invalid('', value, state,
149 raise formencode.Invalid('', value, state,
150 error_dict=e_dict)
150 error_dict=e_dict)
151
151
152 return _ValidReposGroup
152 return _ValidReposGroup
153
153
154 class ValidPassword(formencode.validators.FancyValidator):
154 class ValidPassword(formencode.validators.FancyValidator):
155
155
156 def to_python(self, value, state):
156 def to_python(self, value, state):
157
157
158 if value:
158 if value:
159
159
160 if value.get('password'):
160 if value.get('password'):
161 try:
161 try:
162 value['password'] = get_crypt_password(value['password'])
162 value['password'] = get_crypt_password(value['password'])
163 except UnicodeEncodeError:
163 except UnicodeEncodeError:
164 e_dict = {'password':_('Invalid characters in password')}
164 e_dict = {'password':_('Invalid characters in password')}
165 raise formencode.Invalid('', value, state, error_dict=e_dict)
165 raise formencode.Invalid('', value, state, error_dict=e_dict)
166
166
167 if value.get('password_confirmation'):
167 if value.get('password_confirmation'):
168 try:
168 try:
169 value['password_confirmation'] = \
169 value['password_confirmation'] = \
170 get_crypt_password(value['password_confirmation'])
170 get_crypt_password(value['password_confirmation'])
171 except UnicodeEncodeError:
171 except UnicodeEncodeError:
172 e_dict = {'password_confirmation':_('Invalid characters in password')}
172 e_dict = {'password_confirmation':_('Invalid characters in password')}
173 raise formencode.Invalid('', value, state, error_dict=e_dict)
173 raise formencode.Invalid('', value, state, error_dict=e_dict)
174
174
175 if value.get('new_password'):
175 if value.get('new_password'):
176 try:
176 try:
177 value['new_password'] = \
177 value['new_password'] = \
178 get_crypt_password(value['new_password'])
178 get_crypt_password(value['new_password'])
179 except UnicodeEncodeError:
179 except UnicodeEncodeError:
180 e_dict = {'new_password':_('Invalid characters in password')}
180 e_dict = {'new_password':_('Invalid characters in password')}
181 raise formencode.Invalid('', value, state, error_dict=e_dict)
181 raise formencode.Invalid('', value, state, error_dict=e_dict)
182
182
183 return value
183 return value
184
184
185 class ValidPasswordsMatch(formencode.validators.FancyValidator):
185 class ValidPasswordsMatch(formencode.validators.FancyValidator):
186
186
187 def validate_python(self, value, state):
187 def validate_python(self, value, state):
188
188
189 if value['password'] != value['password_confirmation']:
189 if value['password'] != value['password_confirmation']:
190 e_dict = {'password_confirmation':
190 e_dict = {'password_confirmation':
191 _('Passwords do not match')}
191 _('Passwords do not match')}
192 raise formencode.Invalid('', value, state, error_dict=e_dict)
192 raise formencode.Invalid('', value, state, error_dict=e_dict)
193
193
194 class ValidAuth(formencode.validators.FancyValidator):
194 class ValidAuth(formencode.validators.FancyValidator):
195 messages = {
195 messages = {
196 'invalid_password':_('invalid password'),
196 'invalid_password':_('invalid password'),
197 'invalid_login':_('invalid user name'),
197 'invalid_login':_('invalid user name'),
198 'disabled_account':_('Your account is disabled')
198 'disabled_account':_('Your account is disabled')
199
199
200 }
200 }
201 #error mapping
201 #error mapping
202 e_dict = {'username':messages['invalid_login'],
202 e_dict = {'username':messages['invalid_login'],
203 'password':messages['invalid_password']}
203 'password':messages['invalid_password']}
204 e_dict_disable = {'username':messages['disabled_account']}
204 e_dict_disable = {'username':messages['disabled_account']}
205
205
206 def validate_python(self, value, state):
206 def validate_python(self, value, state):
207 password = value['password']
207 password = value['password']
208 username = value['username']
208 username = value['username']
209 user = UserModel().get_by_username(username)
209 user = User.get_by_username(username)
210
210
211 if authenticate(username, password):
211 if authenticate(username, password):
212 return value
212 return value
213 else:
213 else:
214 if user and user.active is False:
214 if user and user.active is False:
215 log.warning('user %s is disabled', username)
215 log.warning('user %s is disabled', username)
216 raise formencode.Invalid(self.message('disabled_account',
216 raise formencode.Invalid(self.message('disabled_account',
217 state=State_obj),
217 state=State_obj),
218 value, state,
218 value, state,
219 error_dict=self.e_dict_disable)
219 error_dict=self.e_dict_disable)
220 else:
220 else:
221 log.warning('user %s not authenticated', username)
221 log.warning('user %s not authenticated', username)
222 raise formencode.Invalid(self.message('invalid_password',
222 raise formencode.Invalid(self.message('invalid_password',
223 state=State_obj), value, state,
223 state=State_obj), value, state,
224 error_dict=self.e_dict)
224 error_dict=self.e_dict)
225
225
226 class ValidRepoUser(formencode.validators.FancyValidator):
226 class ValidRepoUser(formencode.validators.FancyValidator):
227
227
228 def to_python(self, value, state):
228 def to_python(self, value, state):
229 try:
229 try:
230 User.query().filter(User.active == True)\
230 User.query().filter(User.active == True)\
231 .filter(User.username == value).one()
231 .filter(User.username == value).one()
232 except Exception:
232 except Exception:
233 raise formencode.Invalid(_('This username is not valid'),
233 raise formencode.Invalid(_('This username is not valid'),
234 value, state)
234 value, state)
235 return value
235 return value
236
236
237 def ValidRepoName(edit, old_data):
237 def ValidRepoName(edit, old_data):
238 class _ValidRepoName(formencode.validators.FancyValidator):
238 class _ValidRepoName(formencode.validators.FancyValidator):
239 def to_python(self, value, state):
239 def to_python(self, value, state):
240
240
241 repo_name = value.get('repo_name')
241 repo_name = value.get('repo_name')
242
242
243 slug = repo_name_slug(repo_name)
243 slug = repo_name_slug(repo_name)
244 if slug in ['_admin', '']:
244 if slug in [ADMIN_PREFIX, '']:
245 e_dict = {'repo_name': _('This repository name is disallowed')}
245 e_dict = {'repo_name': _('This repository name is disallowed')}
246 raise formencode.Invalid('', value, state, error_dict=e_dict)
246 raise formencode.Invalid('', value, state, error_dict=e_dict)
247
247
248
248
249 if value.get('repo_group'):
249 if value.get('repo_group'):
250 gr = Group.get(value.get('repo_group'))
250 gr = Group.get(value.get('repo_group'))
251 group_path = gr.full_path
251 group_path = gr.full_path
252 # value needs to be aware of group name in order to check
252 # value needs to be aware of group name in order to check
253 # db key This is an actuall just the name to store in the
253 # db key This is an actual just the name to store in the
254 # database
254 # database
255 repo_name_full = group_path + Group.url_sep() + repo_name
255 repo_name_full = group_path + Group.url_sep() + repo_name
256 else:
256 else:
257 group_path = ''
257 group_path = ''
258 repo_name_full = repo_name
258 repo_name_full = repo_name
259
259
260
260
261 value['repo_name_full'] = repo_name_full
261 value['repo_name_full'] = repo_name_full
262 if old_data.get('repo_name') != repo_name_full or not edit:
262 rename = old_data.get('repo_name') != repo_name_full
263 create = not edit
264 if rename or create:
263
265
264 if group_path != '':
266 if group_path != '':
265 if RepoModel().get_by_repo_name(repo_name_full,):
267 if RepoModel().get_by_repo_name(repo_name_full,):
266 e_dict = {'repo_name':_('This repository already '
268 e_dict = {'repo_name':_('This repository already '
267 'exists in group "%s"') %
269 'exists in a group "%s"') %
268 gr.group_name}
270 gr.group_name}
269 raise formencode.Invalid('', value, state,
271 raise formencode.Invalid('', value, state,
270 error_dict=e_dict)
272 error_dict=e_dict)
273 elif Group.get_by_group_name(repo_name_full):
274 e_dict = {'repo_name':_('There is a group with this'
275 ' name already "%s"') %
276 repo_name_full}
277 raise formencode.Invalid('', value, state,
278 error_dict=e_dict)
271
279
272 else:
280 elif RepoModel().get_by_repo_name(repo_name_full):
273 if RepoModel().get_by_repo_name(repo_name_full):
274 e_dict = {'repo_name':_('This repository '
281 e_dict = {'repo_name':_('This repository '
275 'already exists')}
282 'already exists')}
276 raise formencode.Invalid('', value, state,
283 raise formencode.Invalid('', value, state,
277 error_dict=e_dict)
284 error_dict=e_dict)
285
278 return value
286 return value
279
287
280
281 return _ValidRepoName
288 return _ValidRepoName
282
289
283 def ValidForkName():
290 def ValidForkName():
284 class _ValidForkName(formencode.validators.FancyValidator):
291 class _ValidForkName(formencode.validators.FancyValidator):
285 def to_python(self, value, state):
292 def to_python(self, value, state):
286
293
287 repo_name = value.get('fork_name')
294 repo_name = value.get('fork_name')
288
295
289 slug = repo_name_slug(repo_name)
296 slug = repo_name_slug(repo_name)
290 if slug in ['_admin', '']:
297 if slug in [ADMIN_PREFIX, '']:
291 e_dict = {'repo_name': _('This repository name is disallowed')}
298 e_dict = {'repo_name': _('This repository name is disallowed')}
292 raise formencode.Invalid('', value, state, error_dict=e_dict)
299 raise formencode.Invalid('', value, state, error_dict=e_dict)
293
300
294 if RepoModel().get_by_repo_name(repo_name):
301 if RepoModel().get_by_repo_name(repo_name):
295 e_dict = {'fork_name':_('This repository '
302 e_dict = {'fork_name':_('This repository '
296 'already exists')}
303 'already exists')}
297 raise formencode.Invalid('', value, state,
304 raise formencode.Invalid('', value, state,
298 error_dict=e_dict)
305 error_dict=e_dict)
299 return value
306 return value
300 return _ValidForkName
307 return _ValidForkName
301
308
302
309
303 def SlugifyName():
310 def SlugifyName():
304 class _SlugifyName(formencode.validators.FancyValidator):
311 class _SlugifyName(formencode.validators.FancyValidator):
305
312
306 def to_python(self, value, state):
313 def to_python(self, value, state):
307 return repo_name_slug(value)
314 return repo_name_slug(value)
308
315
309 return _SlugifyName
316 return _SlugifyName
310
317
311 def ValidCloneUri():
318 def ValidCloneUri():
312 from mercurial.httprepo import httprepository, httpsrepository
319 from mercurial.httprepo import httprepository, httpsrepository
313 from rhodecode.lib.utils import make_ui
320 from rhodecode.lib.utils import make_ui
314
321
315 class _ValidCloneUri(formencode.validators.FancyValidator):
322 class _ValidCloneUri(formencode.validators.FancyValidator):
316
323
317 def to_python(self, value, state):
324 def to_python(self, value, state):
318 if not value:
325 if not value:
319 pass
326 pass
320 elif value.startswith('https'):
327 elif value.startswith('https'):
321 try:
328 try:
322 httpsrepository(make_ui('db'), value).capabilities
329 httpsrepository(make_ui('db'), value).capabilities
323 except Exception, e:
330 except Exception, e:
324 log.error(traceback.format_exc())
331 log.error(traceback.format_exc())
325 raise formencode.Invalid(_('invalid clone url'), value,
332 raise formencode.Invalid(_('invalid clone url'), value,
326 state)
333 state)
327 elif value.startswith('http'):
334 elif value.startswith('http'):
328 try:
335 try:
329 httprepository(make_ui('db'), value).capabilities
336 httprepository(make_ui('db'), value).capabilities
330 except Exception, e:
337 except Exception, e:
331 log.error(traceback.format_exc())
338 log.error(traceback.format_exc())
332 raise formencode.Invalid(_('invalid clone url'), value,
339 raise formencode.Invalid(_('invalid clone url'), value,
333 state)
340 state)
334 else:
341 else:
335 raise formencode.Invalid(_('Invalid clone url, provide a '
342 raise formencode.Invalid(_('Invalid clone url, provide a '
336 'valid clone http\s url'), value,
343 'valid clone http\s url'), value,
337 state)
344 state)
338 return value
345 return value
339
346
340 return _ValidCloneUri
347 return _ValidCloneUri
341
348
342 def ValidForkType(old_data):
349 def ValidForkType(old_data):
343 class _ValidForkType(formencode.validators.FancyValidator):
350 class _ValidForkType(formencode.validators.FancyValidator):
344
351
345 def to_python(self, value, state):
352 def to_python(self, value, state):
346 if old_data['repo_type'] != value:
353 if old_data['repo_type'] != value:
347 raise formencode.Invalid(_('Fork have to be the same '
354 raise formencode.Invalid(_('Fork have to be the same '
348 'type as original'), value, state)
355 'type as original'), value, state)
349
356
350 return value
357 return value
351 return _ValidForkType
358 return _ValidForkType
352
359
353 class ValidPerms(formencode.validators.FancyValidator):
360 class ValidPerms(formencode.validators.FancyValidator):
354 messages = {'perm_new_member_name':_('This username or users group name'
361 messages = {'perm_new_member_name':_('This username or users group name'
355 ' is not valid')}
362 ' is not valid')}
356
363
357 def to_python(self, value, state):
364 def to_python(self, value, state):
358 perms_update = []
365 perms_update = []
359 perms_new = []
366 perms_new = []
360 #build a list of permission to update and new permission to create
367 #build a list of permission to update and new permission to create
361 for k, v in value.items():
368 for k, v in value.items():
362 #means new added member to permissions
369 #means new added member to permissions
363 if k.startswith('perm_new_member'):
370 if k.startswith('perm_new_member'):
364 new_perm = value.get('perm_new_member', False)
371 new_perm = value.get('perm_new_member', False)
365 new_member = value.get('perm_new_member_name', False)
372 new_member = value.get('perm_new_member_name', False)
366 new_type = value.get('perm_new_member_type')
373 new_type = value.get('perm_new_member_type')
367
374
368 if new_member and new_perm:
375 if new_member and new_perm:
369 if (new_member, new_perm, new_type) not in perms_new:
376 if (new_member, new_perm, new_type) not in perms_new:
370 perms_new.append((new_member, new_perm, new_type))
377 perms_new.append((new_member, new_perm, new_type))
371 elif k.startswith('u_perm_') or k.startswith('g_perm_'):
378 elif k.startswith('u_perm_') or k.startswith('g_perm_'):
372 member = k[7:]
379 member = k[7:]
373 t = {'u':'user',
380 t = {'u':'user',
374 'g':'users_group'}[k[0]]
381 'g':'users_group'}[k[0]]
375 if member == 'default':
382 if member == 'default':
376 if value['private']:
383 if value['private']:
377 #set none for default when updating to private repo
384 #set none for default when updating to private repo
378 v = 'repository.none'
385 v = 'repository.none'
379 perms_update.append((member, v, t))
386 perms_update.append((member, v, t))
380
387
381 value['perms_updates'] = perms_update
388 value['perms_updates'] = perms_update
382 value['perms_new'] = perms_new
389 value['perms_new'] = perms_new
383
390
384 #update permissions
391 #update permissions
385 for k, v, t in perms_new:
392 for k, v, t in perms_new:
386 try:
393 try:
387 if t is 'user':
394 if t is 'user':
388 self.user_db = User.query()\
395 self.user_db = User.query()\
389 .filter(User.active == True)\
396 .filter(User.active == True)\
390 .filter(User.username == k).one()
397 .filter(User.username == k).one()
391 if t is 'users_group':
398 if t is 'users_group':
392 self.user_db = UsersGroup.query()\
399 self.user_db = UsersGroup.query()\
393 .filter(UsersGroup.users_group_active == True)\
400 .filter(UsersGroup.users_group_active == True)\
394 .filter(UsersGroup.users_group_name == k).one()
401 .filter(UsersGroup.users_group_name == k).one()
395
402
396 except Exception:
403 except Exception:
397 msg = self.message('perm_new_member_name',
404 msg = self.message('perm_new_member_name',
398 state=State_obj)
405 state=State_obj)
399 raise formencode.Invalid(msg, value, state,
406 raise formencode.Invalid(msg, value, state,
400 error_dict={'perm_new_member_name':msg})
407 error_dict={'perm_new_member_name':msg})
401 return value
408 return value
402
409
403 class ValidSettings(formencode.validators.FancyValidator):
410 class ValidSettings(formencode.validators.FancyValidator):
404
411
405 def to_python(self, value, state):
412 def to_python(self, value, state):
406 #settings form can't edit user
413 #settings form can't edit user
407 if value.has_key('user'):
414 if value.has_key('user'):
408 del['value']['user']
415 del['value']['user']
409
416
410 return value
417 return value
411
418
412 class ValidPath(formencode.validators.FancyValidator):
419 class ValidPath(formencode.validators.FancyValidator):
413 def to_python(self, value, state):
420 def to_python(self, value, state):
414
421
415 if not os.path.isdir(value):
422 if not os.path.isdir(value):
416 msg = _('This is not a valid path')
423 msg = _('This is not a valid path')
417 raise formencode.Invalid(msg, value, state,
424 raise formencode.Invalid(msg, value, state,
418 error_dict={'paths_root_path':msg})
425 error_dict={'paths_root_path':msg})
419 return value
426 return value
420
427
421 def UniqSystemEmail(old_data):
428 def UniqSystemEmail(old_data):
422 class _UniqSystemEmail(formencode.validators.FancyValidator):
429 class _UniqSystemEmail(formencode.validators.FancyValidator):
423 def to_python(self, value, state):
430 def to_python(self, value, state):
424 value = value.lower()
431 value = value.lower()
425 if old_data.get('email') != value:
432 if old_data.get('email') != value:
426 user = User.query().filter(User.email == value).scalar()
433 user = User.query().filter(User.email == value).scalar()
427 if user:
434 if user:
428 raise formencode.Invalid(
435 raise formencode.Invalid(
429 _("This e-mail address is already taken"),
436 _("This e-mail address is already taken"),
430 value, state)
437 value, state)
431 return value
438 return value
432
439
433 return _UniqSystemEmail
440 return _UniqSystemEmail
434
441
435 class ValidSystemEmail(formencode.validators.FancyValidator):
442 class ValidSystemEmail(formencode.validators.FancyValidator):
436 def to_python(self, value, state):
443 def to_python(self, value, state):
437 value = value.lower()
444 value = value.lower()
438 user = User.query().filter(User.email == value).scalar()
445 user = User.query().filter(User.email == value).scalar()
439 if user is None:
446 if user is None:
440 raise formencode.Invalid(_("This e-mail address doesn't exist.") ,
447 raise formencode.Invalid(_("This e-mail address doesn't exist.") ,
441 value, state)
448 value, state)
442
449
443 return value
450 return value
444
451
445 class LdapLibValidator(formencode.validators.FancyValidator):
452 class LdapLibValidator(formencode.validators.FancyValidator):
446
453
447 def to_python(self, value, state):
454 def to_python(self, value, state):
448
455
449 try:
456 try:
450 import ldap
457 import ldap
451 except ImportError:
458 except ImportError:
452 raise LdapImportError
459 raise LdapImportError
453 return value
460 return value
454
461
455 class AttrLoginValidator(formencode.validators.FancyValidator):
462 class AttrLoginValidator(formencode.validators.FancyValidator):
456
463
457 def to_python(self, value, state):
464 def to_python(self, value, state):
458
465
459 if not value or not isinstance(value, (str, unicode)):
466 if not value or not isinstance(value, (str, unicode)):
460 raise formencode.Invalid(_("The LDAP Login attribute of the CN "
467 raise formencode.Invalid(_("The LDAP Login attribute of the CN "
461 "must be specified - this is the name "
468 "must be specified - this is the name "
462 "of the attribute that is equivalent "
469 "of the attribute that is equivalent "
463 "to 'username'"),
470 "to 'username'"),
464 value, state)
471 value, state)
465
472
466 return value
473 return value
467
474
468 #===============================================================================
475 #===============================================================================
469 # FORMS
476 # FORMS
470 #===============================================================================
477 #===============================================================================
471 class LoginForm(formencode.Schema):
478 class LoginForm(formencode.Schema):
472 allow_extra_fields = True
479 allow_extra_fields = True
473 filter_extra_fields = True
480 filter_extra_fields = True
474 username = UnicodeString(
481 username = UnicodeString(
475 strip=True,
482 strip=True,
476 min=1,
483 min=1,
477 not_empty=True,
484 not_empty=True,
478 messages={
485 messages={
479 'empty':_('Please enter a login'),
486 'empty':_('Please enter a login'),
480 'tooShort':_('Enter a value %(min)i characters long or more')}
487 'tooShort':_('Enter a value %(min)i characters long or more')}
481 )
488 )
482
489
483 password = UnicodeString(
490 password = UnicodeString(
484 strip=True,
491 strip=True,
485 min=3,
492 min=3,
486 not_empty=True,
493 not_empty=True,
487 messages={
494 messages={
488 'empty':_('Please enter a password'),
495 'empty':_('Please enter a password'),
489 'tooShort':_('Enter %(min)i characters or more')}
496 'tooShort':_('Enter %(min)i characters or more')}
490 )
497 )
491
498
492
499
493 #chained validators have access to all data
500 #chained validators have access to all data
494 chained_validators = [ValidAuth]
501 chained_validators = [ValidAuth]
495
502
496 def UserForm(edit=False, old_data={}):
503 def UserForm(edit=False, old_data={}):
497 class _UserForm(formencode.Schema):
504 class _UserForm(formencode.Schema):
498 allow_extra_fields = True
505 allow_extra_fields = True
499 filter_extra_fields = True
506 filter_extra_fields = True
500 username = All(UnicodeString(strip=True, min=1, not_empty=True),
507 username = All(UnicodeString(strip=True, min=1, not_empty=True),
501 ValidUsername(edit, old_data))
508 ValidUsername(edit, old_data))
502 if edit:
509 if edit:
503 new_password = All(UnicodeString(strip=True, min=6, not_empty=False))
510 new_password = All(UnicodeString(strip=True, min=6, not_empty=False))
504 admin = StringBoolean(if_missing=False)
511 admin = StringBoolean(if_missing=False)
505 else:
512 else:
506 password = All(UnicodeString(strip=True, min=6, not_empty=True))
513 password = All(UnicodeString(strip=True, min=6, not_empty=True))
507 active = StringBoolean(if_missing=False)
514 active = StringBoolean(if_missing=False)
508 name = UnicodeString(strip=True, min=1, not_empty=True)
515 name = UnicodeString(strip=True, min=1, not_empty=True)
509 lastname = UnicodeString(strip=True, min=1, not_empty=True)
516 lastname = UnicodeString(strip=True, min=1, not_empty=True)
510 email = All(Email(not_empty=True), UniqSystemEmail(old_data))
517 email = All(Email(not_empty=True), UniqSystemEmail(old_data))
511
518
512 chained_validators = [ValidPassword]
519 chained_validators = [ValidPassword]
513
520
514 return _UserForm
521 return _UserForm
515
522
516
523
517 def UsersGroupForm(edit=False, old_data={}, available_members=[]):
524 def UsersGroupForm(edit=False, old_data={}, available_members=[]):
518 class _UsersGroupForm(formencode.Schema):
525 class _UsersGroupForm(formencode.Schema):
519 allow_extra_fields = True
526 allow_extra_fields = True
520 filter_extra_fields = True
527 filter_extra_fields = True
521
528
522 users_group_name = All(UnicodeString(strip=True, min=1, not_empty=True),
529 users_group_name = All(UnicodeString(strip=True, min=1, not_empty=True),
523 ValidUsersGroup(edit, old_data))
530 ValidUsersGroup(edit, old_data))
524
531
525 users_group_active = StringBoolean(if_missing=False)
532 users_group_active = StringBoolean(if_missing=False)
526
533
527 if edit:
534 if edit:
528 users_group_members = OneOf(available_members, hideList=False,
535 users_group_members = OneOf(available_members, hideList=False,
529 testValueList=True,
536 testValueList=True,
530 if_missing=None, not_empty=False)
537 if_missing=None, not_empty=False)
531
538
532 return _UsersGroupForm
539 return _UsersGroupForm
533
540
534 def ReposGroupForm(edit=False, old_data={}, available_groups=[]):
541 def ReposGroupForm(edit=False, old_data={}, available_groups=[]):
535 class _ReposGroupForm(formencode.Schema):
542 class _ReposGroupForm(formencode.Schema):
536 allow_extra_fields = True
543 allow_extra_fields = True
537 filter_extra_fields = True
544 filter_extra_fields = True
538
545
539 group_name = All(UnicodeString(strip=True, min=1, not_empty=True),
546 group_name = All(UnicodeString(strip=True, min=1, not_empty=True),
540 SlugifyName())
547 SlugifyName())
541 group_description = UnicodeString(strip=True, min=1,
548 group_description = UnicodeString(strip=True, min=1,
542 not_empty=True)
549 not_empty=True)
543 group_parent_id = OneOf(available_groups, hideList=False,
550 group_parent_id = OneOf(available_groups, hideList=False,
544 testValueList=True,
551 testValueList=True,
545 if_missing=None, not_empty=False)
552 if_missing=None, not_empty=False)
546
553
547 chained_validators = [ValidReposGroup(edit, old_data)]
554 chained_validators = [ValidReposGroup(edit, old_data)]
548
555
549 return _ReposGroupForm
556 return _ReposGroupForm
550
557
551 def RegisterForm(edit=False, old_data={}):
558 def RegisterForm(edit=False, old_data={}):
552 class _RegisterForm(formencode.Schema):
559 class _RegisterForm(formencode.Schema):
553 allow_extra_fields = True
560 allow_extra_fields = True
554 filter_extra_fields = True
561 filter_extra_fields = True
555 username = All(ValidUsername(edit, old_data),
562 username = All(ValidUsername(edit, old_data),
556 UnicodeString(strip=True, min=1, not_empty=True))
563 UnicodeString(strip=True, min=1, not_empty=True))
557 password = All(UnicodeString(strip=True, min=6, not_empty=True))
564 password = All(UnicodeString(strip=True, min=6, not_empty=True))
558 password_confirmation = All(UnicodeString(strip=True, min=6, not_empty=True))
565 password_confirmation = All(UnicodeString(strip=True, min=6, not_empty=True))
559 active = StringBoolean(if_missing=False)
566 active = StringBoolean(if_missing=False)
560 name = UnicodeString(strip=True, min=1, not_empty=True)
567 name = UnicodeString(strip=True, min=1, not_empty=True)
561 lastname = UnicodeString(strip=True, min=1, not_empty=True)
568 lastname = UnicodeString(strip=True, min=1, not_empty=True)
562 email = All(Email(not_empty=True), UniqSystemEmail(old_data))
569 email = All(Email(not_empty=True), UniqSystemEmail(old_data))
563
570
564 chained_validators = [ValidPasswordsMatch, ValidPassword]
571 chained_validators = [ValidPasswordsMatch, ValidPassword]
565
572
566 return _RegisterForm
573 return _RegisterForm
567
574
568 def PasswordResetForm():
575 def PasswordResetForm():
569 class _PasswordResetForm(formencode.Schema):
576 class _PasswordResetForm(formencode.Schema):
570 allow_extra_fields = True
577 allow_extra_fields = True
571 filter_extra_fields = True
578 filter_extra_fields = True
572 email = All(ValidSystemEmail(), Email(not_empty=True))
579 email = All(ValidSystemEmail(), Email(not_empty=True))
573 return _PasswordResetForm
580 return _PasswordResetForm
574
581
575 def RepoForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
582 def RepoForm(edit=False, old_data={}, supported_backends=BACKENDS.keys(),
576 repo_groups=[]):
583 repo_groups=[]):
577 class _RepoForm(formencode.Schema):
584 class _RepoForm(formencode.Schema):
578 allow_extra_fields = True
585 allow_extra_fields = True
579 filter_extra_fields = False
586 filter_extra_fields = False
580 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
587 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
581 SlugifyName())
588 SlugifyName())
582 clone_uri = All(UnicodeString(strip=True, min=1, not_empty=False),
589 clone_uri = All(UnicodeString(strip=True, min=1, not_empty=False),
583 ValidCloneUri()())
590 ValidCloneUri()())
584 repo_group = OneOf(repo_groups, hideList=True)
591 repo_group = OneOf(repo_groups, hideList=True)
585 repo_type = OneOf(supported_backends)
592 repo_type = OneOf(supported_backends)
586 description = UnicodeString(strip=True, min=1, not_empty=True)
593 description = UnicodeString(strip=True, min=1, not_empty=True)
587 private = StringBoolean(if_missing=False)
594 private = StringBoolean(if_missing=False)
588 enable_statistics = StringBoolean(if_missing=False)
595 enable_statistics = StringBoolean(if_missing=False)
589 enable_downloads = StringBoolean(if_missing=False)
596 enable_downloads = StringBoolean(if_missing=False)
590
597
591 if edit:
598 if edit:
592 #this is repo owner
599 #this is repo owner
593 user = All(UnicodeString(not_empty=True), ValidRepoUser)
600 user = All(UnicodeString(not_empty=True), ValidRepoUser)
594
601
595 chained_validators = [ValidRepoName(edit, old_data), ValidPerms]
602 chained_validators = [ValidRepoName(edit, old_data), ValidPerms]
596 return _RepoForm
603 return _RepoForm
597
604
598 def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys()):
605 def RepoForkForm(edit=False, old_data={}, supported_backends=BACKENDS.keys()):
599 class _RepoForkForm(formencode.Schema):
606 class _RepoForkForm(formencode.Schema):
600 allow_extra_fields = True
607 allow_extra_fields = True
601 filter_extra_fields = False
608 filter_extra_fields = False
602 fork_name = All(UnicodeString(strip=True, min=1, not_empty=True),
609 fork_name = All(UnicodeString(strip=True, min=1, not_empty=True),
603 SlugifyName())
610 SlugifyName())
604 description = UnicodeString(strip=True, min=1, not_empty=True)
611 description = UnicodeString(strip=True, min=1, not_empty=True)
605 private = StringBoolean(if_missing=False)
612 private = StringBoolean(if_missing=False)
606 repo_type = All(ValidForkType(old_data), OneOf(supported_backends))
613 repo_type = All(ValidForkType(old_data), OneOf(supported_backends))
607
614
608 chained_validators = [ValidForkName()]
615 chained_validators = [ValidForkName()]
609
616
610 return _RepoForkForm
617 return _RepoForkForm
611
618
612 def RepoSettingsForm(edit=False, old_data={}):
619 def RepoSettingsForm(edit=False, old_data={}):
613 class _RepoForm(formencode.Schema):
620 class _RepoForm(formencode.Schema):
614 allow_extra_fields = True
621 allow_extra_fields = True
615 filter_extra_fields = False
622 filter_extra_fields = False
616 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
623 repo_name = All(UnicodeString(strip=True, min=1, not_empty=True),
617 SlugifyName())
624 SlugifyName())
618 description = UnicodeString(strip=True, min=1, not_empty=True)
625 description = UnicodeString(strip=True, min=1, not_empty=True)
619 private = StringBoolean(if_missing=False)
626 private = StringBoolean(if_missing=False)
620
627
621 chained_validators = [ValidRepoName(edit, old_data), ValidPerms, ValidSettings]
628 chained_validators = [ValidRepoName(edit, old_data), ValidPerms, ValidSettings]
622 return _RepoForm
629 return _RepoForm
623
630
624
631
625 def ApplicationSettingsForm():
632 def ApplicationSettingsForm():
626 class _ApplicationSettingsForm(formencode.Schema):
633 class _ApplicationSettingsForm(formencode.Schema):
627 allow_extra_fields = True
634 allow_extra_fields = True
628 filter_extra_fields = False
635 filter_extra_fields = False
629 rhodecode_title = UnicodeString(strip=True, min=1, not_empty=True)
636 rhodecode_title = UnicodeString(strip=True, min=1, not_empty=True)
630 rhodecode_realm = UnicodeString(strip=True, min=1, not_empty=True)
637 rhodecode_realm = UnicodeString(strip=True, min=1, not_empty=True)
631 rhodecode_ga_code = UnicodeString(strip=True, min=1, not_empty=False)
638 rhodecode_ga_code = UnicodeString(strip=True, min=1, not_empty=False)
632
639
633 return _ApplicationSettingsForm
640 return _ApplicationSettingsForm
634
641
635 def ApplicationUiSettingsForm():
642 def ApplicationUiSettingsForm():
636 class _ApplicationUiSettingsForm(formencode.Schema):
643 class _ApplicationUiSettingsForm(formencode.Schema):
637 allow_extra_fields = True
644 allow_extra_fields = True
638 filter_extra_fields = False
645 filter_extra_fields = False
639 web_push_ssl = OneOf(['true', 'false'], if_missing='false')
646 web_push_ssl = OneOf(['true', 'false'], if_missing='false')
640 paths_root_path = All(ValidPath(), UnicodeString(strip=True, min=1, not_empty=True))
647 paths_root_path = All(ValidPath(), UnicodeString(strip=True, min=1, not_empty=True))
641 hooks_changegroup_update = OneOf(['True', 'False'], if_missing=False)
648 hooks_changegroup_update = OneOf(['True', 'False'], if_missing=False)
642 hooks_changegroup_repo_size = OneOf(['True', 'False'], if_missing=False)
649 hooks_changegroup_repo_size = OneOf(['True', 'False'], if_missing=False)
643 hooks_pretxnchangegroup_push_logger = OneOf(['True', 'False'], if_missing=False)
650 hooks_pretxnchangegroup_push_logger = OneOf(['True', 'False'], if_missing=False)
644 hooks_preoutgoing_pull_logger = OneOf(['True', 'False'], if_missing=False)
651 hooks_preoutgoing_pull_logger = OneOf(['True', 'False'], if_missing=False)
645
652
646 return _ApplicationUiSettingsForm
653 return _ApplicationUiSettingsForm
647
654
648 def DefaultPermissionsForm(perms_choices, register_choices, create_choices):
655 def DefaultPermissionsForm(perms_choices, register_choices, create_choices):
649 class _DefaultPermissionsForm(formencode.Schema):
656 class _DefaultPermissionsForm(formencode.Schema):
650 allow_extra_fields = True
657 allow_extra_fields = True
651 filter_extra_fields = True
658 filter_extra_fields = True
652 overwrite_default = StringBoolean(if_missing=False)
659 overwrite_default = StringBoolean(if_missing=False)
653 anonymous = OneOf(['True', 'False'], if_missing=False)
660 anonymous = OneOf(['True', 'False'], if_missing=False)
654 default_perm = OneOf(perms_choices)
661 default_perm = OneOf(perms_choices)
655 default_register = OneOf(register_choices)
662 default_register = OneOf(register_choices)
656 default_create = OneOf(create_choices)
663 default_create = OneOf(create_choices)
657
664
658 return _DefaultPermissionsForm
665 return _DefaultPermissionsForm
659
666
660
667
661 def LdapSettingsForm(tls_reqcert_choices, search_scope_choices, tls_kind_choices):
668 def LdapSettingsForm(tls_reqcert_choices, search_scope_choices, tls_kind_choices):
662 class _LdapSettingsForm(formencode.Schema):
669 class _LdapSettingsForm(formencode.Schema):
663 allow_extra_fields = True
670 allow_extra_fields = True
664 filter_extra_fields = True
671 filter_extra_fields = True
665 pre_validators = [LdapLibValidator]
672 pre_validators = [LdapLibValidator]
666 ldap_active = StringBoolean(if_missing=False)
673 ldap_active = StringBoolean(if_missing=False)
667 ldap_host = UnicodeString(strip=True,)
674 ldap_host = UnicodeString(strip=True,)
668 ldap_port = Number(strip=True,)
675 ldap_port = Number(strip=True,)
669 ldap_tls_kind = OneOf(tls_kind_choices)
676 ldap_tls_kind = OneOf(tls_kind_choices)
670 ldap_tls_reqcert = OneOf(tls_reqcert_choices)
677 ldap_tls_reqcert = OneOf(tls_reqcert_choices)
671 ldap_dn_user = UnicodeString(strip=True,)
678 ldap_dn_user = UnicodeString(strip=True,)
672 ldap_dn_pass = UnicodeString(strip=True,)
679 ldap_dn_pass = UnicodeString(strip=True,)
673 ldap_base_dn = UnicodeString(strip=True,)
680 ldap_base_dn = UnicodeString(strip=True,)
674 ldap_filter = UnicodeString(strip=True,)
681 ldap_filter = UnicodeString(strip=True,)
675 ldap_search_scope = OneOf(search_scope_choices)
682 ldap_search_scope = OneOf(search_scope_choices)
676 ldap_attr_login = All(AttrLoginValidator, UnicodeString(strip=True,))
683 ldap_attr_login = All(AttrLoginValidator, UnicodeString(strip=True,))
677 ldap_attr_firstname = UnicodeString(strip=True,)
684 ldap_attr_firstname = UnicodeString(strip=True,)
678 ldap_attr_lastname = UnicodeString(strip=True,)
685 ldap_attr_lastname = UnicodeString(strip=True,)
679 ldap_attr_email = UnicodeString(strip=True,)
686 ldap_attr_email = UnicodeString(strip=True,)
680
687
681 return _LdapSettingsForm
688 return _LdapSettingsForm
@@ -1,361 +1,370
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.repo
3 rhodecode.model.repo
4 ~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~
5
5
6 Repository model for rhodecode
6 Repository model for rhodecode
7
7
8 :created_on: Jun 5, 2010
8 :created_on: Jun 5, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 import os
25 import os
26 import shutil
26 import shutil
27 import logging
27 import logging
28 import traceback
28 import traceback
29 from datetime import datetime
29 from datetime import datetime
30
30
31 from sqlalchemy.orm import joinedload, make_transient
31 from sqlalchemy.orm import joinedload, make_transient
32
32
33 from vcs.utils.lazy import LazyProperty
33 from vcs.utils.lazy import LazyProperty
34 from vcs.backends import get_backend
34 from vcs.backends import get_backend
35
35
36 from rhodecode.lib import safe_str
36 from rhodecode.lib import safe_str
37
37
38 from rhodecode.model import BaseModel
38 from rhodecode.model import BaseModel
39 from rhodecode.model.caching_query import FromCache
39 from rhodecode.model.caching_query import FromCache
40 from rhodecode.model.db import Repository, RepoToPerm, User, Permission, \
40 from rhodecode.model.db import Repository, RepoToPerm, User, Permission, \
41 Statistics, UsersGroup, UsersGroupRepoToPerm, RhodeCodeUi, Group
41 Statistics, UsersGroup, UsersGroupRepoToPerm, RhodeCodeUi, Group
42 from rhodecode.model.user import UserModel
42 from rhodecode.model.user import UserModel
43
43
44 log = logging.getLogger(__name__)
44 log = logging.getLogger(__name__)
45
45
46
46
47 class RepoModel(BaseModel):
47 class RepoModel(BaseModel):
48
48
49 @LazyProperty
49 @LazyProperty
50 def repos_path(self):
50 def repos_path(self):
51 """Get's the repositories root path from database
51 """Get's the repositories root path from database
52 """
52 """
53
53
54 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
54 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
55 return q.ui_value
55 return q.ui_value
56
56
57 def get(self, repo_id, cache=False):
57 def get(self, repo_id, cache=False):
58 repo = self.sa.query(Repository)\
58 repo = self.sa.query(Repository)\
59 .filter(Repository.repo_id == repo_id)
59 .filter(Repository.repo_id == repo_id)
60
60
61 if cache:
61 if cache:
62 repo = repo.options(FromCache("sql_cache_short",
62 repo = repo.options(FromCache("sql_cache_short",
63 "get_repo_%s" % repo_id))
63 "get_repo_%s" % repo_id))
64 return repo.scalar()
64 return repo.scalar()
65
65
66 def get_by_repo_name(self, repo_name, cache=False):
66 def get_by_repo_name(self, repo_name, cache=False):
67 repo = self.sa.query(Repository)\
67 repo = self.sa.query(Repository)\
68 .filter(Repository.repo_name == repo_name)
68 .filter(Repository.repo_name == repo_name)
69
69
70 if cache:
70 if cache:
71 repo = repo.options(FromCache("sql_cache_short",
71 repo = repo.options(FromCache("sql_cache_short",
72 "get_repo_%s" % repo_name))
72 "get_repo_%s" % repo_name))
73 return repo.scalar()
73 return repo.scalar()
74
74
75
75
76 def get_users_js(self):
76 def get_users_js(self):
77
77
78 users = self.sa.query(User).filter(User.active == True).all()
78 users = self.sa.query(User).filter(User.active == True).all()
79 u_tmpl = '''{id:%s, fname:"%s", lname:"%s", nname:"%s"},'''
79 u_tmpl = '''{id:%s, fname:"%s", lname:"%s", nname:"%s"},'''
80 users_array = '[%s]' % '\n'.join([u_tmpl % (u.user_id, u.name,
80 users_array = '[%s]' % '\n'.join([u_tmpl % (u.user_id, u.name,
81 u.lastname, u.username)
81 u.lastname, u.username)
82 for u in users])
82 for u in users])
83 return users_array
83 return users_array
84
84
85 def get_users_groups_js(self):
85 def get_users_groups_js(self):
86 users_groups = self.sa.query(UsersGroup)\
86 users_groups = self.sa.query(UsersGroup)\
87 .filter(UsersGroup.users_group_active == True).all()
87 .filter(UsersGroup.users_group_active == True).all()
88
88
89 g_tmpl = '''{id:%s, grname:"%s",grmembers:"%s"},'''
89 g_tmpl = '''{id:%s, grname:"%s",grmembers:"%s"},'''
90
90
91 users_groups_array = '[%s]' % '\n'.join([g_tmpl % \
91 users_groups_array = '[%s]' % '\n'.join([g_tmpl % \
92 (gr.users_group_id, gr.users_group_name,
92 (gr.users_group_id, gr.users_group_name,
93 len(gr.members))
93 len(gr.members))
94 for gr in users_groups])
94 for gr in users_groups])
95 return users_groups_array
95 return users_groups_array
96
96
97 def update(self, repo_name, form_data):
97 def update(self, repo_name, form_data):
98 try:
98 try:
99 cur_repo = self.get_by_repo_name(repo_name, cache=False)
99 cur_repo = self.get_by_repo_name(repo_name, cache=False)
100
100
101 #update permissions
101 # update permissions
102 for member, perm, member_type in form_data['perms_updates']:
102 for member, perm, member_type in form_data['perms_updates']:
103 if member_type == 'user':
103 if member_type == 'user':
104 r2p = self.sa.query(RepoToPerm)\
104 r2p = self.sa.query(RepoToPerm)\
105 .filter(RepoToPerm.user == User.by_username(member))\
105 .filter(RepoToPerm.user == User.get_by_username(member))\
106 .filter(RepoToPerm.repository == cur_repo)\
106 .filter(RepoToPerm.repository == cur_repo)\
107 .one()
107 .one()
108
108
109 r2p.permission = self.sa.query(Permission)\
109 r2p.permission = self.sa.query(Permission)\
110 .filter(Permission.permission_name ==
110 .filter(Permission.permission_name ==
111 perm).scalar()
111 perm).scalar()
112 self.sa.add(r2p)
112 self.sa.add(r2p)
113 else:
113 else:
114 g2p = self.sa.query(UsersGroupRepoToPerm)\
114 g2p = self.sa.query(UsersGroupRepoToPerm)\
115 .filter(UsersGroupRepoToPerm.users_group ==
115 .filter(UsersGroupRepoToPerm.users_group ==
116 UsersGroup.get_by_group_name(member))\
116 UsersGroup.get_by_group_name(member))\
117 .filter(UsersGroupRepoToPerm.repository ==
117 .filter(UsersGroupRepoToPerm.repository ==
118 cur_repo).one()
118 cur_repo).one()
119
119
120 g2p.permission = self.sa.query(Permission)\
120 g2p.permission = self.sa.query(Permission)\
121 .filter(Permission.permission_name ==
121 .filter(Permission.permission_name ==
122 perm).scalar()
122 perm).scalar()
123 self.sa.add(g2p)
123 self.sa.add(g2p)
124
124
125 #set new permissions
125 # set new permissions
126 for member, perm, member_type in form_data['perms_new']:
126 for member, perm, member_type in form_data['perms_new']:
127 if member_type == 'user':
127 if member_type == 'user':
128 r2p = RepoToPerm()
128 r2p = RepoToPerm()
129 r2p.repository = cur_repo
129 r2p.repository = cur_repo
130 r2p.user = User.by_username(member)
130 r2p.user = User.get_by_username(member)
131
131
132 r2p.permission = self.sa.query(Permission)\
132 r2p.permission = self.sa.query(Permission)\
133 .filter(Permission.
133 .filter(Permission.
134 permission_name == perm)\
134 permission_name == perm)\
135 .scalar()
135 .scalar()
136 self.sa.add(r2p)
136 self.sa.add(r2p)
137 else:
137 else:
138 g2p = UsersGroupRepoToPerm()
138 g2p = UsersGroupRepoToPerm()
139 g2p.repository = cur_repo
139 g2p.repository = cur_repo
140 g2p.users_group = UsersGroup.get_by_group_name(member)
140 g2p.users_group = UsersGroup.get_by_group_name(member)
141 g2p.permission = self.sa.query(Permission)\
141 g2p.permission = self.sa.query(Permission)\
142 .filter(Permission.
142 .filter(Permission.
143 permission_name == perm)\
143 permission_name == perm)\
144 .scalar()
144 .scalar()
145 self.sa.add(g2p)
145 self.sa.add(g2p)
146
146
147 #update current repo
147 # update current repo
148 for k, v in form_data.items():
148 for k, v in form_data.items():
149 if k == 'user':
149 if k == 'user':
150 cur_repo.user = User.by_username(v)
150 cur_repo.user = User.get_by_username(v)
151 elif k == 'repo_name':
151 elif k == 'repo_name':
152 cur_repo.repo_name = form_data['repo_name_full']
152 pass
153 elif k == 'repo_group':
153 elif k == 'repo_group':
154 cur_repo.group_id = v
154 cur_repo.group_id = v
155
155
156 else:
156 else:
157 setattr(cur_repo, k, v)
157 setattr(cur_repo, k, v)
158
158
159 new_name = cur_repo.get_new_name(form_data['repo_name'])
160 cur_repo.repo_name = new_name
161
159 self.sa.add(cur_repo)
162 self.sa.add(cur_repo)
160
163
161 if repo_name != form_data['repo_name_full']:
164 if repo_name != new_name:
162 # rename repository
165 # rename repository
163 self.__rename_repo(old=repo_name,
166 self.__rename_repo(old=repo_name, new=new_name)
164 new=form_data['repo_name_full'])
165
167
166 self.sa.commit()
168 self.sa.commit()
169 return cur_repo
167 except:
170 except:
168 log.error(traceback.format_exc())
171 log.error(traceback.format_exc())
169 self.sa.rollback()
172 self.sa.rollback()
170 raise
173 raise
171
174
172 def create(self, form_data, cur_user, just_db=False, fork=False):
175 def create(self, form_data, cur_user, just_db=False, fork=False):
173
176
174 try:
177 try:
175 if fork:
178 if fork:
176 repo_name = form_data['fork_name']
179 repo_name = form_data['fork_name']
177 org_name = form_data['repo_name']
180 org_name = form_data['repo_name']
178 org_full_name = org_name
181 org_full_name = org_name
179
182
180 else:
183 else:
181 org_name = repo_name = form_data['repo_name']
184 org_name = repo_name = form_data['repo_name']
182 repo_name_full = form_data['repo_name_full']
185 repo_name_full = form_data['repo_name_full']
183
186
184 new_repo = Repository()
187 new_repo = Repository()
185 new_repo.enable_statistics = False
188 new_repo.enable_statistics = False
186 for k, v in form_data.items():
189 for k, v in form_data.items():
187 if k == 'repo_name':
190 if k == 'repo_name':
188 if fork:
191 if fork:
189 v = repo_name
192 v = repo_name
190 else:
193 else:
191 v = repo_name_full
194 v = repo_name_full
192 if k == 'repo_group':
195 if k == 'repo_group':
193 k = 'group_id'
196 k = 'group_id'
194
197
195 if k == 'description':
198 if k == 'description':
196 v = v or repo_name
199 v = v or repo_name
197
200
198 setattr(new_repo, k, v)
201 setattr(new_repo, k, v)
199
202
200 if fork:
203 if fork:
201 parent_repo = self.sa.query(Repository)\
204 parent_repo = self.sa.query(Repository)\
202 .filter(Repository.repo_name == org_full_name).one()
205 .filter(Repository.repo_name == org_full_name).one()
203 new_repo.fork = parent_repo
206 new_repo.fork = parent_repo
204
207
205 new_repo.user_id = cur_user.user_id
208 new_repo.user_id = cur_user.user_id
206 self.sa.add(new_repo)
209 self.sa.add(new_repo)
207
210
208 #create default permission
211 #create default permission
209 repo_to_perm = RepoToPerm()
212 repo_to_perm = RepoToPerm()
210 default = 'repository.read'
213 default = 'repository.read'
211 for p in UserModel(self.sa).get_by_username('default',
214 for p in User.get_by_username('default').user_perms:
212 cache=False).user_perms:
213 if p.permission.permission_name.startswith('repository.'):
215 if p.permission.permission_name.startswith('repository.'):
214 default = p.permission.permission_name
216 default = p.permission.permission_name
215 break
217 break
216
218
217 default_perm = 'repository.none' if form_data['private'] else default
219 default_perm = 'repository.none' if form_data['private'] else default
218
220
219 repo_to_perm.permission_id = self.sa.query(Permission)\
221 repo_to_perm.permission_id = self.sa.query(Permission)\
220 .filter(Permission.permission_name == default_perm)\
222 .filter(Permission.permission_name == default_perm)\
221 .one().permission_id
223 .one().permission_id
222
224
223 repo_to_perm.repository = new_repo
225 repo_to_perm.repository = new_repo
224 repo_to_perm.user_id = UserModel(self.sa)\
226 repo_to_perm.user_id = User.get_by_username('default').user_id
225 .get_by_username('default', cache=False).user_id
226
227
227 self.sa.add(repo_to_perm)
228 self.sa.add(repo_to_perm)
228
229
229 if not just_db:
230 if not just_db:
230 self.__create_repo(repo_name, form_data['repo_type'],
231 self.__create_repo(repo_name, form_data['repo_type'],
231 form_data['repo_group'],
232 form_data['repo_group'],
232 form_data['clone_uri'])
233 form_data['clone_uri'])
233
234
234 self.sa.commit()
235 self.sa.commit()
235
236
236 #now automatically start following this repository as owner
237 #now automatically start following this repository as owner
237 from rhodecode.model.scm import ScmModel
238 from rhodecode.model.scm import ScmModel
238 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id,
239 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id,
239 cur_user.user_id)
240 cur_user.user_id)
240
241 return new_repo
241 except:
242 except:
242 log.error(traceback.format_exc())
243 log.error(traceback.format_exc())
243 self.sa.rollback()
244 self.sa.rollback()
244 raise
245 raise
245
246
246 def create_fork(self, form_data, cur_user):
247 def create_fork(self, form_data, cur_user):
247 from rhodecode.lib.celerylib import tasks, run_task
248 from rhodecode.lib.celerylib import tasks, run_task
248 run_task(tasks.create_repo_fork, form_data, cur_user)
249 run_task(tasks.create_repo_fork, form_data, cur_user)
249
250
250 def delete(self, repo):
251 def delete(self, repo):
251 try:
252 try:
252 self.sa.delete(repo)
253 self.sa.delete(repo)
253 self.__delete_repo(repo)
254 self.__delete_repo(repo)
254 self.sa.commit()
255 self.sa.commit()
255 except:
256 except:
256 log.error(traceback.format_exc())
257 log.error(traceback.format_exc())
257 self.sa.rollback()
258 self.sa.rollback()
258 raise
259 raise
259
260
260 def delete_perm_user(self, form_data, repo_name):
261 def delete_perm_user(self, form_data, repo_name):
261 try:
262 try:
262 self.sa.query(RepoToPerm)\
263 self.sa.query(RepoToPerm)\
263 .filter(RepoToPerm.repository \
264 .filter(RepoToPerm.repository \
264 == self.get_by_repo_name(repo_name))\
265 == self.get_by_repo_name(repo_name))\
265 .filter(RepoToPerm.user_id == form_data['user_id']).delete()
266 .filter(RepoToPerm.user_id == form_data['user_id']).delete()
266 self.sa.commit()
267 self.sa.commit()
267 except:
268 except:
268 log.error(traceback.format_exc())
269 log.error(traceback.format_exc())
269 self.sa.rollback()
270 self.sa.rollback()
270 raise
271 raise
271
272
272 def delete_perm_users_group(self, form_data, repo_name):
273 def delete_perm_users_group(self, form_data, repo_name):
273 try:
274 try:
274 self.sa.query(UsersGroupRepoToPerm)\
275 self.sa.query(UsersGroupRepoToPerm)\
275 .filter(UsersGroupRepoToPerm.repository \
276 .filter(UsersGroupRepoToPerm.repository \
276 == self.get_by_repo_name(repo_name))\
277 == self.get_by_repo_name(repo_name))\
277 .filter(UsersGroupRepoToPerm.users_group_id \
278 .filter(UsersGroupRepoToPerm.users_group_id \
278 == form_data['users_group_id']).delete()
279 == form_data['users_group_id']).delete()
279 self.sa.commit()
280 self.sa.commit()
280 except:
281 except:
281 log.error(traceback.format_exc())
282 log.error(traceback.format_exc())
282 self.sa.rollback()
283 self.sa.rollback()
283 raise
284 raise
284
285
285 def delete_stats(self, repo_name):
286 def delete_stats(self, repo_name):
286 try:
287 try:
287 self.sa.query(Statistics)\
288 self.sa.query(Statistics)\
288 .filter(Statistics.repository == \
289 .filter(Statistics.repository == \
289 self.get_by_repo_name(repo_name)).delete()
290 self.get_by_repo_name(repo_name)).delete()
290 self.sa.commit()
291 self.sa.commit()
291 except:
292 except:
292 log.error(traceback.format_exc())
293 log.error(traceback.format_exc())
293 self.sa.rollback()
294 self.sa.rollback()
294 raise
295 raise
295
296
296 def __create_repo(self, repo_name, alias, new_parent_id, clone_uri=False):
297 def __create_repo(self, repo_name, alias, new_parent_id, clone_uri=False):
297 """
298 """
298 makes repository on filesystem. It's group aware means it'll create
299 makes repository on filesystem. It's group aware means it'll create
299 a repository within a group, and alter the paths accordingly of
300 a repository within a group, and alter the paths accordingly of
300 group location
301 group location
301
302
302 :param repo_name:
303 :param repo_name:
303 :param alias:
304 :param alias:
304 :param parent_id:
305 :param parent_id:
305 :param clone_uri:
306 :param clone_uri:
306 """
307 """
307 from rhodecode.lib.utils import is_valid_repo
308 from rhodecode.lib.utils import is_valid_repo,is_valid_repos_group
308
309
309 if new_parent_id:
310 if new_parent_id:
310 paths = Group.get(new_parent_id).full_path.split(Group.url_sep())
311 paths = Group.get(new_parent_id).full_path.split(Group.url_sep())
311 new_parent_path = os.sep.join(paths)
312 new_parent_path = os.sep.join(paths)
312 else:
313 else:
313 new_parent_path = ''
314 new_parent_path = ''
314
315
315 repo_path = os.path.join(*map(lambda x:safe_str(x),
316 repo_path = os.path.join(*map(lambda x:safe_str(x),
316 [self.repos_path, new_parent_path, repo_name]))
317 [self.repos_path, new_parent_path, repo_name]))
317
318
318 if is_valid_repo(repo_path, self.repos_path) is False:
319
319 log.info('creating repo %s in %s @ %s', repo_name, repo_path,
320 # check if this path is not a repository
320 clone_uri)
321 if is_valid_repo(repo_path, self.repos_path):
321 backend = get_backend(alias)
322 raise Exception('This path %s is a valid repository' % repo_path)
322
323
323 backend(repo_path, create=True, src_url=clone_uri)
324 # check if this path is a group
325 if is_valid_repos_group(repo_path, self.repos_path):
326 raise Exception('This path %s is a valid group' % repo_path)
327
328 log.info('creating repo %s in %s @ %s', repo_name, repo_path,
329 clone_uri)
330 backend = get_backend(alias)
331
332 backend(repo_path, create=True, src_url=clone_uri)
324
333
325
334
326 def __rename_repo(self, old, new):
335 def __rename_repo(self, old, new):
327 """
336 """
328 renames repository on filesystem
337 renames repository on filesystem
329
338
330 :param old: old name
339 :param old: old name
331 :param new: new name
340 :param new: new name
332 """
341 """
333 log.info('renaming repo from %s to %s', old, new)
342 log.info('renaming repo from %s to %s', old, new)
334
343
335 old_path = os.path.join(self.repos_path, old)
344 old_path = os.path.join(self.repos_path, old)
336 new_path = os.path.join(self.repos_path, new)
345 new_path = os.path.join(self.repos_path, new)
337 if os.path.isdir(new_path):
346 if os.path.isdir(new_path):
338 raise Exception('Was trying to rename to already existing dir %s' \
347 raise Exception('Was trying to rename to already existing dir %s' \
339 % new_path)
348 % new_path)
340 shutil.move(old_path, new_path)
349 shutil.move(old_path, new_path)
341
350
342 def __delete_repo(self, repo):
351 def __delete_repo(self, repo):
343 """
352 """
344 removes repo from filesystem, the removal is acctually made by
353 removes repo from filesystem, the removal is acctually made by
345 added rm__ prefix into dir, and rename internat .hg/.git dirs so this
354 added rm__ prefix into dir, and rename internat .hg/.git dirs so this
346 repository is no longer valid for rhodecode, can be undeleted later on
355 repository is no longer valid for rhodecode, can be undeleted later on
347 by reverting the renames on this repository
356 by reverting the renames on this repository
348
357
349 :param repo: repo object
358 :param repo: repo object
350 """
359 """
351 rm_path = os.path.join(self.repos_path, repo.repo_name)
360 rm_path = os.path.join(self.repos_path, repo.repo_name)
352 log.info("Removing %s", rm_path)
361 log.info("Removing %s", rm_path)
353 #disable hg/git
362 #disable hg/git
354 alias = repo.repo_type
363 alias = repo.repo_type
355 shutil.move(os.path.join(rm_path, '.%s' % alias),
364 shutil.move(os.path.join(rm_path, '.%s' % alias),
356 os.path.join(rm_path, 'rm__.%s' % alias))
365 os.path.join(rm_path, 'rm__.%s' % alias))
357 #disable repo
366 #disable repo
358 shutil.move(rm_path, os.path.join(self.repos_path, 'rm__%s__%s' \
367 shutil.move(rm_path, os.path.join(self.repos_path, 'rm__%s__%s' \
359 % (datetime.today()\
368 % (datetime.today()\
360 .strftime('%Y%m%d_%H%M%S_%f'),
369 .strftime('%Y%m%d_%H%M%S_%f'),
361 repo.repo_name)))
370 repo.repo_name)))
@@ -1,173 +1,164
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.user_group
3 rhodecode.model.user_group
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 users groups model for RhodeCode
6 users groups model for RhodeCode
7
7
8 :created_on: Jan 25, 2011
8 :created_on: Jan 25, 2011
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import os
26 import os
27 import logging
27 import logging
28 import traceback
28 import traceback
29 import shutil
29 import shutil
30
30
31 from pylons.i18n.translation import _
31 from pylons.i18n.translation import _
32
32
33 from vcs.utils.lazy import LazyProperty
33 from vcs.utils.lazy import LazyProperty
34
34
35 from rhodecode.model import BaseModel
35 from rhodecode.model import BaseModel
36 from rhodecode.model.caching_query import FromCache
36 from rhodecode.model.caching_query import FromCache
37 from rhodecode.model.db import Group, RhodeCodeUi
37 from rhodecode.model.db import Group, RhodeCodeUi
38
38
39 log = logging.getLogger(__name__)
39 log = logging.getLogger(__name__)
40
40
41
41
42 class ReposGroupModel(BaseModel):
42 class ReposGroupModel(BaseModel):
43
43
44 @LazyProperty
44 @LazyProperty
45 def repos_path(self):
45 def repos_path(self):
46 """
46 """
47 Get's the repositories root path from database
47 Get's the repositories root path from database
48 """
48 """
49
49
50 q = RhodeCodeUi.get_by_key('/').one()
50 q = RhodeCodeUi.get_by_key('/').one()
51 return q.ui_value
51 return q.ui_value
52
52
53 def __create_group(self, group_name, parent_id):
53 def __create_group(self, group_name):
54 """
54 """
55 makes repositories group on filesystem
55 makes repositories group on filesystem
56
56
57 :param repo_name:
57 :param repo_name:
58 :param parent_id:
58 :param parent_id:
59 """
59 """
60
60
61 if parent_id:
61 create_path = os.path.join(self.repos_path, group_name)
62 paths = Group.get(parent_id).full_path.split(Group.url_sep())
63 parent_path = os.sep.join(paths)
64 else:
65 parent_path = ''
66
67 create_path = os.path.join(self.repos_path, parent_path, group_name)
68 log.debug('creating new group in %s', create_path)
62 log.debug('creating new group in %s', create_path)
69
63
70 if os.path.isdir(create_path):
64 if os.path.isdir(create_path):
71 raise Exception('That directory already exists !')
65 raise Exception('That directory already exists !')
72
66
73
74 os.makedirs(create_path)
67 os.makedirs(create_path)
75
68
76
69 def __rename_group(self, old, new):
77 def __rename_group(self, old, old_parent_id, new, new_parent_id):
78 """
70 """
79 Renames a group on filesystem
71 Renames a group on filesystem
80
72
81 :param group_name:
73 :param group_name:
82 """
74 """
75
76 if old == new:
77 log.debug('skipping group rename')
78 return
79
83 log.debug('renaming repos group from %s to %s', old, new)
80 log.debug('renaming repos group from %s to %s', old, new)
84
81
85 if new_parent_id:
86 paths = Group.get(new_parent_id).full_path.split(Group.url_sep())
87 new_parent_path = os.sep.join(paths)
88 else:
89 new_parent_path = ''
90
82
91 if old_parent_id:
83 old_path = os.path.join(self.repos_path, old)
92 paths = Group.get(old_parent_id).full_path.split(Group.url_sep())
84 new_path = os.path.join(self.repos_path, new)
93 old_parent_path = os.sep.join(paths)
94 else:
95 old_parent_path = ''
96
97 old_path = os.path.join(self.repos_path, old_parent_path, old)
98 new_path = os.path.join(self.repos_path, new_parent_path, new)
99
85
100 log.debug('renaming repos paths from %s to %s', old_path, new_path)
86 log.debug('renaming repos paths from %s to %s', old_path, new_path)
101
87
102 if os.path.isdir(new_path):
88 if os.path.isdir(new_path):
103 raise Exception('Was trying to rename to already '
89 raise Exception('Was trying to rename to already '
104 'existing dir %s' % new_path)
90 'existing dir %s' % new_path)
105 shutil.move(old_path, new_path)
91 shutil.move(old_path, new_path)
106
92
107 def __delete_group(self, group):
93 def __delete_group(self, group):
108 """
94 """
109 Deletes a group from a filesystem
95 Deletes a group from a filesystem
110
96
111 :param group: instance of group from database
97 :param group: instance of group from database
112 """
98 """
113 paths = group.full_path.split(Group.url_sep())
99 paths = group.full_path.split(Group.url_sep())
114 paths = os.sep.join(paths)
100 paths = os.sep.join(paths)
115
101
116 rm_path = os.path.join(self.repos_path, paths)
102 rm_path = os.path.join(self.repos_path, paths)
117 os.rmdir(rm_path)
103 if os.path.isdir(rm_path):
104 # delete only if that path really exists
105 os.rmdir(rm_path)
118
106
119 def create(self, form_data):
107 def create(self, form_data):
120 try:
108 try:
121 new_repos_group = Group()
109 new_repos_group = Group()
122 new_repos_group.group_name = form_data['group_name']
110 new_repos_group.group_description = form_data['group_description']
123 new_repos_group.group_description = \
111 new_repos_group.parent_group = Group.get(form_data['group_parent_id'])
124 form_data['group_description']
112 new_repos_group.group_name = new_repos_group.get_new_name(form_data['group_name'])
125 new_repos_group.group_parent_id = form_data['group_parent_id']
126
113
127 self.sa.add(new_repos_group)
114 self.sa.add(new_repos_group)
128
115
129 self.__create_group(form_data['group_name'],
116 self.__create_group(new_repos_group.group_name)
130 form_data['group_parent_id'])
131
117
132 self.sa.commit()
118 self.sa.commit()
119 return new_repos_group
133 except:
120 except:
134 log.error(traceback.format_exc())
121 log.error(traceback.format_exc())
135 self.sa.rollback()
122 self.sa.rollback()
136 raise
123 raise
137
124
138 def update(self, repos_group_id, form_data):
125 def update(self, repos_group_id, form_data):
139
126
140 try:
127 try:
141 repos_group = Group.get(repos_group_id)
128 repos_group = Group.get(repos_group_id)
142 old_name = repos_group.group_name
129 old_path = repos_group.full_path
143 old_parent_id = repos_group.group_parent_id
144
130
145 repos_group.group_name = form_data['group_name']
131 #change properties
146 repos_group.group_description = \
132 repos_group.group_description = form_data['group_description']
147 form_data['group_description']
133 repos_group.parent_group = Group.get(form_data['group_parent_id'])
148 repos_group.group_parent_id = form_data['group_parent_id']
134 repos_group.group_name = repos_group.get_new_name(form_data['group_name'])
135
136 new_path = repos_group.full_path
149
137
150 self.sa.add(repos_group)
138 self.sa.add(repos_group)
151
139
152 if old_name != form_data['group_name'] or (old_parent_id !=
140 self.__rename_group(old_path, new_path)
153 form_data['group_parent_id']):
141
154 self.__rename_group(old=old_name, old_parent_id=old_parent_id,
142 # we need to get all repositories from this new group and
155 new=form_data['group_name'],
143 # rename them accordingly to new group path
156 new_parent_id=form_data['group_parent_id'])
144 for r in repos_group.repositories:
145 r.repo_name = r.get_new_name(r.just_name)
146 self.sa.add(r)
157
147
158 self.sa.commit()
148 self.sa.commit()
149 return repos_group
159 except:
150 except:
160 log.error(traceback.format_exc())
151 log.error(traceback.format_exc())
161 self.sa.rollback()
152 self.sa.rollback()
162 raise
153 raise
163
154
164 def delete(self, users_group_id):
155 def delete(self, users_group_id):
165 try:
156 try:
166 users_group = Group.get(users_group_id)
157 users_group = Group.get(users_group_id)
167 self.sa.delete(users_group)
158 self.sa.delete(users_group)
168 self.__delete_group(users_group)
159 self.__delete_group(users_group)
169 self.sa.commit()
160 self.sa.commit()
170 except:
161 except:
171 log.error(traceback.format_exc())
162 log.error(traceback.format_exc())
172 self.sa.rollback()
163 self.sa.rollback()
173 raise
164 raise
@@ -1,410 +1,414
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.scm
3 rhodecode.model.scm
4 ~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~
5
5
6 Scm model for RhodeCode
6 Scm model for RhodeCode
7
7
8 :created_on: Apr 9, 2010
8 :created_on: Apr 9, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 import os
25 import os
26 import time
26 import time
27 import traceback
27 import traceback
28 import logging
28 import logging
29
29
30 from sqlalchemy.exc import DatabaseError
30 from sqlalchemy.exc import DatabaseError
31
31
32 from vcs import get_backend
32 from vcs import get_backend
33 from vcs.exceptions import RepositoryError
33 from vcs.exceptions import RepositoryError
34 from vcs.utils.lazy import LazyProperty
34 from vcs.utils.lazy import LazyProperty
35 from vcs.nodes import FileNode
35 from vcs.nodes import FileNode
36
36
37 from rhodecode import BACKENDS
37 from rhodecode import BACKENDS
38 from rhodecode.lib import helpers as h
38 from rhodecode.lib import helpers as h
39 from rhodecode.lib import safe_str
39 from rhodecode.lib import safe_str
40 from rhodecode.lib.auth import HasRepoPermissionAny
40 from rhodecode.lib.auth import HasRepoPermissionAny
41 from rhodecode.lib.utils import get_repos as get_filesystem_repos, make_ui, \
41 from rhodecode.lib.utils import get_repos as get_filesystem_repos, make_ui, \
42 action_logger, EmptyChangeset
42 action_logger, EmptyChangeset
43 from rhodecode.model import BaseModel
43 from rhodecode.model import BaseModel
44 from rhodecode.model.user import UserModel
45 from rhodecode.model.db import Repository, RhodeCodeUi, CacheInvalidation, \
44 from rhodecode.model.db import Repository, RhodeCodeUi, CacheInvalidation, \
46 UserFollowing, UserLog
45 UserFollowing, UserLog, User
47
46
48 log = logging.getLogger(__name__)
47 log = logging.getLogger(__name__)
49
48
50
49
51 class UserTemp(object):
50 class UserTemp(object):
52 def __init__(self, user_id):
51 def __init__(self, user_id):
53 self.user_id = user_id
52 self.user_id = user_id
54
53
55 def __repr__(self):
54 def __repr__(self):
56 return "<%s('id:%s')>" % (self.__class__.__name__, self.user_id)
55 return "<%s('id:%s')>" % (self.__class__.__name__, self.user_id)
57
56
58
57
59 class RepoTemp(object):
58 class RepoTemp(object):
60 def __init__(self, repo_id):
59 def __init__(self, repo_id):
61 self.repo_id = repo_id
60 self.repo_id = repo_id
62
61
63 def __repr__(self):
62 def __repr__(self):
64 return "<%s('id:%s')>" % (self.__class__.__name__, self.repo_id)
63 return "<%s('id:%s')>" % (self.__class__.__name__, self.repo_id)
65
64
66 class CachedRepoList(object):
65 class CachedRepoList(object):
67
66
68 def __init__(self, db_repo_list, repos_path, order_by=None):
67 def __init__(self, db_repo_list, repos_path, order_by=None):
69 self.db_repo_list = db_repo_list
68 self.db_repo_list = db_repo_list
70 self.repos_path = repos_path
69 self.repos_path = repos_path
71 self.order_by = order_by
70 self.order_by = order_by
72 self.reversed = (order_by or '').startswith('-')
71 self.reversed = (order_by or '').startswith('-')
73
72
74 def __len__(self):
73 def __len__(self):
75 return len(self.db_repo_list)
74 return len(self.db_repo_list)
76
75
77 def __repr__(self):
76 def __repr__(self):
78 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
77 return '<%s (%s)>' % (self.__class__.__name__, self.__len__())
79
78
80 def __iter__(self):
79 def __iter__(self):
81 for dbr in self.db_repo_list:
80 for dbr in self.db_repo_list:
82
81
83 scmr = dbr.scm_instance_cached
82 scmr = dbr.scm_instance_cached
84
83
85 # check permission at this level
84 # check permission at this level
86 if not HasRepoPermissionAny('repository.read', 'repository.write',
85 if not HasRepoPermissionAny('repository.read', 'repository.write',
87 'repository.admin')(dbr.repo_name,
86 'repository.admin')(dbr.repo_name,
88 'get repo check'):
87 'get repo check'):
89 continue
88 continue
90
89
91 if scmr is None:
90 if scmr is None:
92 log.error('%s this repository is present in database but it '
91 log.error('%s this repository is present in database but it '
93 'cannot be created as an scm instance',
92 'cannot be created as an scm instance',
94 dbr.repo_name)
93 dbr.repo_name)
95 continue
94 continue
96
95
97 last_change = scmr.last_change
96 last_change = scmr.last_change
98 tip = h.get_changeset_safe(scmr, 'tip')
97 tip = h.get_changeset_safe(scmr, 'tip')
99
98
100 tmp_d = {}
99 tmp_d = {}
101 tmp_d['name'] = dbr.repo_name
100 tmp_d['name'] = dbr.repo_name
102 tmp_d['name_sort'] = tmp_d['name'].lower()
101 tmp_d['name_sort'] = tmp_d['name'].lower()
103 tmp_d['description'] = dbr.description
102 tmp_d['description'] = dbr.description
104 tmp_d['description_sort'] = tmp_d['description']
103 tmp_d['description_sort'] = tmp_d['description']
105 tmp_d['last_change'] = last_change
104 tmp_d['last_change'] = last_change
106 tmp_d['last_change_sort'] = time.mktime(last_change \
105 tmp_d['last_change_sort'] = time.mktime(last_change \
107 .timetuple())
106 .timetuple())
108 tmp_d['tip'] = tip.raw_id
107 tmp_d['tip'] = tip.raw_id
109 tmp_d['tip_sort'] = tip.revision
108 tmp_d['tip_sort'] = tip.revision
110 tmp_d['rev'] = tip.revision
109 tmp_d['rev'] = tip.revision
111 tmp_d['contact'] = dbr.user.full_contact
110 tmp_d['contact'] = dbr.user.full_contact
112 tmp_d['contact_sort'] = tmp_d['contact']
111 tmp_d['contact_sort'] = tmp_d['contact']
113 tmp_d['owner_sort'] = tmp_d['contact']
112 tmp_d['owner_sort'] = tmp_d['contact']
114 tmp_d['repo_archives'] = list(scmr._get_archives())
113 tmp_d['repo_archives'] = list(scmr._get_archives())
115 tmp_d['last_msg'] = tip.message
114 tmp_d['last_msg'] = tip.message
116 tmp_d['author'] = tip.author
115 tmp_d['author'] = tip.author
117 tmp_d['dbrepo'] = dbr.get_dict()
116 tmp_d['dbrepo'] = dbr.get_dict()
118 tmp_d['dbrepo_fork'] = dbr.fork.get_dict() if dbr.fork \
117 tmp_d['dbrepo_fork'] = dbr.fork.get_dict() if dbr.fork \
119 else {}
118 else {}
120 yield tmp_d
119 yield tmp_d
121
120
122 class ScmModel(BaseModel):
121 class ScmModel(BaseModel):
123 """Generic Scm Model
122 """Generic Scm Model
124 """
123 """
125
124
126 @LazyProperty
125 @LazyProperty
127 def repos_path(self):
126 def repos_path(self):
128 """Get's the repositories root path from database
127 """Get's the repositories root path from database
129 """
128 """
130
129
131 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
130 q = self.sa.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key == '/').one()
132
131
133 return q.ui_value
132 return q.ui_value
134
133
135 def repo_scan(self, repos_path=None):
134 def repo_scan(self, repos_path=None):
136 """Listing of repositories in given path. This path should not be a
135 """Listing of repositories in given path. This path should not be a
137 repository itself. Return a dictionary of repository objects
136 repository itself. Return a dictionary of repository objects
138
137
139 :param repos_path: path to directory containing repositories
138 :param repos_path: path to directory containing repositories
140 """
139 """
141
140
142 log.info('scanning for repositories in %s', repos_path)
141 log.info('scanning for repositories in %s', repos_path)
143
142
144 if repos_path is None:
143 if repos_path is None:
145 repos_path = self.repos_path
144 repos_path = self.repos_path
146
145
147 baseui = make_ui('db')
146 baseui = make_ui('db')
148 repos_list = {}
147 repos_list = {}
149
148
150 for name, path in get_filesystem_repos(repos_path, recursive=True):
149 for name, path in get_filesystem_repos(repos_path, recursive=True):
150
151 # name need to be decomposed and put back together using the /
152 # since this is internal storage separator for rhodecode
153 name = Repository.url_sep().join(name.split(os.sep))
154
151 try:
155 try:
152 if name in repos_list:
156 if name in repos_list:
153 raise RepositoryError('Duplicate repository name %s '
157 raise RepositoryError('Duplicate repository name %s '
154 'found in %s' % (name, path))
158 'found in %s' % (name, path))
155 else:
159 else:
156
160
157 klass = get_backend(path[0])
161 klass = get_backend(path[0])
158
162
159 if path[0] == 'hg' and path[0] in BACKENDS.keys():
163 if path[0] == 'hg' and path[0] in BACKENDS.keys():
160
164
161 # for mercurial we need to have an str path
165 # for mercurial we need to have an str path
162 repos_list[name] = klass(safe_str(path[1]),
166 repos_list[name] = klass(safe_str(path[1]),
163 baseui=baseui)
167 baseui=baseui)
164
168
165 if path[0] == 'git' and path[0] in BACKENDS.keys():
169 if path[0] == 'git' and path[0] in BACKENDS.keys():
166 repos_list[name] = klass(path[1])
170 repos_list[name] = klass(path[1])
167 except OSError:
171 except OSError:
168 continue
172 continue
169
173
170 return repos_list
174 return repos_list
171
175
172 def get_repos(self, all_repos=None, sort_key=None):
176 def get_repos(self, all_repos=None, sort_key=None):
173 """
177 """
174 Get all repos from db and for each repo create it's
178 Get all repos from db and for each repo create it's
175 backend instance and fill that backed with information from database
179 backend instance and fill that backed with information from database
176
180
177 :param all_repos: list of repository names as strings
181 :param all_repos: list of repository names as strings
178 give specific repositories list, good for filtering
182 give specific repositories list, good for filtering
179 """
183 """
180 if all_repos is None:
184 if all_repos is None:
181 all_repos = self.sa.query(Repository)\
185 all_repos = self.sa.query(Repository)\
182 .filter(Repository.group_id == None)\
186 .filter(Repository.group_id == None)\
183 .order_by(Repository.repo_name).all()
187 .order_by(Repository.repo_name).all()
184
188
185 repo_iter = CachedRepoList(all_repos, repos_path=self.repos_path,
189 repo_iter = CachedRepoList(all_repos, repos_path=self.repos_path,
186 order_by=sort_key)
190 order_by=sort_key)
187
191
188 return repo_iter
192 return repo_iter
189
193
190 def mark_for_invalidation(self, repo_name):
194 def mark_for_invalidation(self, repo_name):
191 """Puts cache invalidation task into db for
195 """Puts cache invalidation task into db for
192 further global cache invalidation
196 further global cache invalidation
193
197
194 :param repo_name: this repo that should invalidation take place
198 :param repo_name: this repo that should invalidation take place
195 """
199 """
196
200
197 log.debug('marking %s for invalidation', repo_name)
201 log.debug('marking %s for invalidation', repo_name)
198 cache = self.sa.query(CacheInvalidation)\
202 cache = self.sa.query(CacheInvalidation)\
199 .filter(CacheInvalidation.cache_key == repo_name).scalar()
203 .filter(CacheInvalidation.cache_key == repo_name).scalar()
200
204
201 if cache:
205 if cache:
202 # mark this cache as inactive
206 # mark this cache as inactive
203 cache.cache_active = False
207 cache.cache_active = False
204 else:
208 else:
205 log.debug('cache key not found in invalidation db -> creating one')
209 log.debug('cache key not found in invalidation db -> creating one')
206 cache = CacheInvalidation(repo_name)
210 cache = CacheInvalidation(repo_name)
207
211
208 try:
212 try:
209 self.sa.add(cache)
213 self.sa.add(cache)
210 self.sa.commit()
214 self.sa.commit()
211 except (DatabaseError,):
215 except (DatabaseError,):
212 log.error(traceback.format_exc())
216 log.error(traceback.format_exc())
213 self.sa.rollback()
217 self.sa.rollback()
214
218
215 def toggle_following_repo(self, follow_repo_id, user_id):
219 def toggle_following_repo(self, follow_repo_id, user_id):
216
220
217 f = self.sa.query(UserFollowing)\
221 f = self.sa.query(UserFollowing)\
218 .filter(UserFollowing.follows_repo_id == follow_repo_id)\
222 .filter(UserFollowing.follows_repo_id == follow_repo_id)\
219 .filter(UserFollowing.user_id == user_id).scalar()
223 .filter(UserFollowing.user_id == user_id).scalar()
220
224
221 if f is not None:
225 if f is not None:
222
226
223 try:
227 try:
224 self.sa.delete(f)
228 self.sa.delete(f)
225 self.sa.commit()
229 self.sa.commit()
226 action_logger(UserTemp(user_id),
230 action_logger(UserTemp(user_id),
227 'stopped_following_repo',
231 'stopped_following_repo',
228 RepoTemp(follow_repo_id))
232 RepoTemp(follow_repo_id))
229 return
233 return
230 except:
234 except:
231 log.error(traceback.format_exc())
235 log.error(traceback.format_exc())
232 self.sa.rollback()
236 self.sa.rollback()
233 raise
237 raise
234
238
235 try:
239 try:
236 f = UserFollowing()
240 f = UserFollowing()
237 f.user_id = user_id
241 f.user_id = user_id
238 f.follows_repo_id = follow_repo_id
242 f.follows_repo_id = follow_repo_id
239 self.sa.add(f)
243 self.sa.add(f)
240 self.sa.commit()
244 self.sa.commit()
241 action_logger(UserTemp(user_id),
245 action_logger(UserTemp(user_id),
242 'started_following_repo',
246 'started_following_repo',
243 RepoTemp(follow_repo_id))
247 RepoTemp(follow_repo_id))
244 except:
248 except:
245 log.error(traceback.format_exc())
249 log.error(traceback.format_exc())
246 self.sa.rollback()
250 self.sa.rollback()
247 raise
251 raise
248
252
249 def toggle_following_user(self, follow_user_id, user_id):
253 def toggle_following_user(self, follow_user_id, user_id):
250 f = self.sa.query(UserFollowing)\
254 f = self.sa.query(UserFollowing)\
251 .filter(UserFollowing.follows_user_id == follow_user_id)\
255 .filter(UserFollowing.follows_user_id == follow_user_id)\
252 .filter(UserFollowing.user_id == user_id).scalar()
256 .filter(UserFollowing.user_id == user_id).scalar()
253
257
254 if f is not None:
258 if f is not None:
255 try:
259 try:
256 self.sa.delete(f)
260 self.sa.delete(f)
257 self.sa.commit()
261 self.sa.commit()
258 return
262 return
259 except:
263 except:
260 log.error(traceback.format_exc())
264 log.error(traceback.format_exc())
261 self.sa.rollback()
265 self.sa.rollback()
262 raise
266 raise
263
267
264 try:
268 try:
265 f = UserFollowing()
269 f = UserFollowing()
266 f.user_id = user_id
270 f.user_id = user_id
267 f.follows_user_id = follow_user_id
271 f.follows_user_id = follow_user_id
268 self.sa.add(f)
272 self.sa.add(f)
269 self.sa.commit()
273 self.sa.commit()
270 except:
274 except:
271 log.error(traceback.format_exc())
275 log.error(traceback.format_exc())
272 self.sa.rollback()
276 self.sa.rollback()
273 raise
277 raise
274
278
275 def is_following_repo(self, repo_name, user_id, cache=False):
279 def is_following_repo(self, repo_name, user_id, cache=False):
276 r = self.sa.query(Repository)\
280 r = self.sa.query(Repository)\
277 .filter(Repository.repo_name == repo_name).scalar()
281 .filter(Repository.repo_name == repo_name).scalar()
278
282
279 f = self.sa.query(UserFollowing)\
283 f = self.sa.query(UserFollowing)\
280 .filter(UserFollowing.follows_repository == r)\
284 .filter(UserFollowing.follows_repository == r)\
281 .filter(UserFollowing.user_id == user_id).scalar()
285 .filter(UserFollowing.user_id == user_id).scalar()
282
286
283 return f is not None
287 return f is not None
284
288
285 def is_following_user(self, username, user_id, cache=False):
289 def is_following_user(self, username, user_id, cache=False):
286 u = UserModel(self.sa).get_by_username(username)
290 u = User.get_by_username(username)
287
291
288 f = self.sa.query(UserFollowing)\
292 f = self.sa.query(UserFollowing)\
289 .filter(UserFollowing.follows_user == u)\
293 .filter(UserFollowing.follows_user == u)\
290 .filter(UserFollowing.user_id == user_id).scalar()
294 .filter(UserFollowing.user_id == user_id).scalar()
291
295
292 return f is not None
296 return f is not None
293
297
294 def get_followers(self, repo_id):
298 def get_followers(self, repo_id):
295 if not isinstance(repo_id, int):
299 if not isinstance(repo_id, int):
296 repo_id = getattr(Repository.by_repo_name(repo_id), 'repo_id')
300 repo_id = getattr(Repository.get_by_repo_name(repo_id), 'repo_id')
297
301
298 return self.sa.query(UserFollowing)\
302 return self.sa.query(UserFollowing)\
299 .filter(UserFollowing.follows_repo_id == repo_id).count()
303 .filter(UserFollowing.follows_repo_id == repo_id).count()
300
304
301 def get_forks(self, repo_id):
305 def get_forks(self, repo_id):
302 if not isinstance(repo_id, int):
306 if not isinstance(repo_id, int):
303 repo_id = getattr(Repository.by_repo_name(repo_id), 'repo_id')
307 repo_id = getattr(Repository.get_by_repo_name(repo_id), 'repo_id')
304
308
305 return self.sa.query(Repository)\
309 return self.sa.query(Repository)\
306 .filter(Repository.fork_id == repo_id).count()
310 .filter(Repository.fork_id == repo_id).count()
307
311
308 def pull_changes(self, repo_name, username):
312 def pull_changes(self, repo_name, username):
309 dbrepo = Repository.by_repo_name(repo_name)
313 dbrepo = Repository.get_by_repo_name(repo_name)
310 clone_uri = dbrepo.clone_uri
314 clone_uri = dbrepo.clone_uri
311 if not clone_uri:
315 if not clone_uri:
312 raise Exception("This repository doesn't have a clone uri")
316 raise Exception("This repository doesn't have a clone uri")
313
317
314 repo = dbrepo.scm_instance
318 repo = dbrepo.scm_instance
315 try:
319 try:
316 extras = {'ip': '',
320 extras = {'ip': '',
317 'username': username,
321 'username': username,
318 'action': 'push_remote',
322 'action': 'push_remote',
319 'repository': repo_name}
323 'repository': repo_name}
320
324
321 #inject ui extra param to log this action via push logger
325 #inject ui extra param to log this action via push logger
322 for k, v in extras.items():
326 for k, v in extras.items():
323 repo._repo.ui.setconfig('rhodecode_extras', k, v)
327 repo._repo.ui.setconfig('rhodecode_extras', k, v)
324
328
325 repo.pull(clone_uri)
329 repo.pull(clone_uri)
326 self.mark_for_invalidation(repo_name)
330 self.mark_for_invalidation(repo_name)
327 except:
331 except:
328 log.error(traceback.format_exc())
332 log.error(traceback.format_exc())
329 raise
333 raise
330
334
331 def commit_change(self, repo, repo_name, cs, user, author, message, content,
335 def commit_change(self, repo, repo_name, cs, user, author, message, content,
332 f_path):
336 f_path):
333
337
334 if repo.alias == 'hg':
338 if repo.alias == 'hg':
335 from vcs.backends.hg import MercurialInMemoryChangeset as IMC
339 from vcs.backends.hg import MercurialInMemoryChangeset as IMC
336 elif repo.alias == 'git':
340 elif repo.alias == 'git':
337 from vcs.backends.git import GitInMemoryChangeset as IMC
341 from vcs.backends.git import GitInMemoryChangeset as IMC
338
342
339 # decoding here will force that we have proper encoded values
343 # decoding here will force that we have proper encoded values
340 # in any other case this will throw exceptions and deny commit
344 # in any other case this will throw exceptions and deny commit
341 content = safe_str(content)
345 content = safe_str(content)
342 message = safe_str(message)
346 message = safe_str(message)
343 path = safe_str(f_path)
347 path = safe_str(f_path)
344 author = safe_str(author)
348 author = safe_str(author)
345 m = IMC(repo)
349 m = IMC(repo)
346 m.change(FileNode(path, content))
350 m.change(FileNode(path, content))
347 tip = m.commit(message=message,
351 tip = m.commit(message=message,
348 author=author,
352 author=author,
349 parents=[cs], branch=cs.branch)
353 parents=[cs], branch=cs.branch)
350
354
351 new_cs = tip.short_id
355 new_cs = tip.short_id
352 action = 'push_local:%s' % new_cs
356 action = 'push_local:%s' % new_cs
353
357
354 action_logger(user, action, repo_name)
358 action_logger(user, action, repo_name)
355
359
356 self.mark_for_invalidation(repo_name)
360 self.mark_for_invalidation(repo_name)
357
361
358 def create_node(self, repo, repo_name, cs, user, author, message, content,
362 def create_node(self, repo, repo_name, cs, user, author, message, content,
359 f_path):
363 f_path):
360 if repo.alias == 'hg':
364 if repo.alias == 'hg':
361 from vcs.backends.hg import MercurialInMemoryChangeset as IMC
365 from vcs.backends.hg import MercurialInMemoryChangeset as IMC
362 elif repo.alias == 'git':
366 elif repo.alias == 'git':
363 from vcs.backends.git import GitInMemoryChangeset as IMC
367 from vcs.backends.git import GitInMemoryChangeset as IMC
364 # decoding here will force that we have proper encoded values
368 # decoding here will force that we have proper encoded values
365 # in any other case this will throw exceptions and deny commit
369 # in any other case this will throw exceptions and deny commit
366
370
367 if isinstance(content,(basestring,)):
371 if isinstance(content, (basestring,)):
368 content = safe_str(content)
372 content = safe_str(content)
369 elif isinstance(content,file):
373 elif isinstance(content, file):
370 content = content.read()
374 content = content.read()
371
375
372 message = safe_str(message)
376 message = safe_str(message)
373 path = safe_str(f_path)
377 path = safe_str(f_path)
374 author = safe_str(author)
378 author = safe_str(author)
375 m = IMC(repo)
379 m = IMC(repo)
376
380
377 if isinstance(cs, EmptyChangeset):
381 if isinstance(cs, EmptyChangeset):
378 # Emptychangeset means we we're editing empty repository
382 # Emptychangeset means we we're editing empty repository
379 parents = None
383 parents = None
380 else:
384 else:
381 parents = [cs]
385 parents = [cs]
382
386
383 m.add(FileNode(path, content=content))
387 m.add(FileNode(path, content=content))
384 tip = m.commit(message=message,
388 tip = m.commit(message=message,
385 author=author,
389 author=author,
386 parents=parents, branch=cs.branch)
390 parents=parents, branch=cs.branch)
387 new_cs = tip.short_id
391 new_cs = tip.short_id
388 action = 'push_local:%s' % new_cs
392 action = 'push_local:%s' % new_cs
389
393
390 action_logger(user, action, repo_name)
394 action_logger(user, action, repo_name)
391
395
392 self.mark_for_invalidation(repo_name)
396 self.mark_for_invalidation(repo_name)
393
397
394
398
395 def get_unread_journal(self):
399 def get_unread_journal(self):
396 return self.sa.query(UserLog).count()
400 return self.sa.query(UserLog).count()
397
401
398 def _should_invalidate(self, repo_name):
402 def _should_invalidate(self, repo_name):
399 """Looks up database for invalidation signals for this repo_name
403 """Looks up database for invalidation signals for this repo_name
400
404
401 :param repo_name:
405 :param repo_name:
402 """
406 """
403
407
404 ret = self.sa.query(CacheInvalidation)\
408 ret = self.sa.query(CacheInvalidation)\
405 .filter(CacheInvalidation.cache_key == repo_name)\
409 .filter(CacheInvalidation.cache_key == repo_name)\
406 .filter(CacheInvalidation.cache_active == False)\
410 .filter(CacheInvalidation.cache_active == False)\
407 .scalar()
411 .scalar()
408
412
409 return ret
413 return ret
410
414
@@ -1,2749 +1,2756
1 html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,font,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td {
1 html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,font,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td {
2 border:0;
2 border:0;
3 outline:0;
3 outline:0;
4 font-size:100%;
4 font-size:100%;
5 vertical-align:baseline;
5 vertical-align:baseline;
6 background:transparent;
6 background:transparent;
7 margin:0;
7 margin:0;
8 padding:0;
8 padding:0;
9 }
9 }
10
10
11 body {
11 body {
12 line-height:1;
12 line-height:1;
13 height:100%;
13 height:100%;
14 background:url("../images/background.png") repeat scroll 0 0 #B0B0B0;
14 background:url("../images/background.png") repeat scroll 0 0 #B0B0B0;
15 font-family:Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
15 font-family:Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
16 font-size:12px;
16 font-size:12px;
17 color:#000;
17 color:#000;
18 margin:0;
18 margin:0;
19 padding:0;
19 padding:0;
20 }
20 }
21
21
22 ol,ul {
22 ol,ul {
23 list-style:none;
23 list-style:none;
24 }
24 }
25
25
26 blockquote,q {
26 blockquote,q {
27 quotes:none;
27 quotes:none;
28 }
28 }
29
29
30 blockquote:before,blockquote:after,q:before,q:after {
30 blockquote:before,blockquote:after,q:before,q:after {
31 content:none;
31 content:none;
32 }
32 }
33
33
34 :focus {
34 :focus {
35 outline:0;
35 outline:0;
36 }
36 }
37
37
38 del {
38 del {
39 text-decoration:line-through;
39 text-decoration:line-through;
40 }
40 }
41
41
42 table {
42 table {
43 border-collapse:collapse;
43 border-collapse:collapse;
44 border-spacing:0;
44 border-spacing:0;
45 }
45 }
46
46
47 html {
47 html {
48 height:100%;
48 height:100%;
49 }
49 }
50
50
51 a {
51 a {
52 color:#003367;
52 color:#003367;
53 text-decoration:none;
53 text-decoration:none;
54 cursor:pointer;
54 cursor:pointer;
55 }
55 }
56
56
57 a:hover {
57 a:hover {
58 color:#316293;
58 color:#316293;
59 text-decoration:underline;
59 text-decoration:underline;
60 }
60 }
61
61
62 h1,h2,h3,h4,h5,h6 {
62 h1,h2,h3,h4,h5,h6 {
63 color:#292929;
63 color:#292929;
64 font-weight:700;
64 font-weight:700;
65 }
65 }
66
66
67 h1 {
67 h1 {
68 font-size:22px;
68 font-size:22px;
69 }
69 }
70
70
71 h2 {
71 h2 {
72 font-size:20px;
72 font-size:20px;
73 }
73 }
74
74
75 h3 {
75 h3 {
76 font-size:18px;
76 font-size:18px;
77 }
77 }
78
78
79 h4 {
79 h4 {
80 font-size:16px;
80 font-size:16px;
81 }
81 }
82
82
83 h5 {
83 h5 {
84 font-size:14px;
84 font-size:14px;
85 }
85 }
86
86
87 h6 {
87 h6 {
88 font-size:11px;
88 font-size:11px;
89 }
89 }
90
90
91 ul.circle {
91 ul.circle {
92 list-style-type:circle;
92 list-style-type:circle;
93 }
93 }
94
94
95 ul.disc {
95 ul.disc {
96 list-style-type:disc;
96 list-style-type:disc;
97 }
97 }
98
98
99 ul.square {
99 ul.square {
100 list-style-type:square;
100 list-style-type:square;
101 }
101 }
102
102
103 ol.lower-roman {
103 ol.lower-roman {
104 list-style-type:lower-roman;
104 list-style-type:lower-roman;
105 }
105 }
106
106
107 ol.upper-roman {
107 ol.upper-roman {
108 list-style-type:upper-roman;
108 list-style-type:upper-roman;
109 }
109 }
110
110
111 ol.lower-alpha {
111 ol.lower-alpha {
112 list-style-type:lower-alpha;
112 list-style-type:lower-alpha;
113 }
113 }
114
114
115 ol.upper-alpha {
115 ol.upper-alpha {
116 list-style-type:upper-alpha;
116 list-style-type:upper-alpha;
117 }
117 }
118
118
119 ol.decimal {
119 ol.decimal {
120 list-style-type:decimal;
120 list-style-type:decimal;
121 }
121 }
122
122
123 div.color {
123 div.color {
124 clear:both;
124 clear:both;
125 overflow:hidden;
125 overflow:hidden;
126 position:absolute;
126 position:absolute;
127 background:#FFF;
127 background:#FFF;
128 margin:7px 0 0 60px;
128 margin:7px 0 0 60px;
129 padding:1px 1px 1px 0;
129 padding:1px 1px 1px 0;
130 }
130 }
131
131
132 div.color a {
132 div.color a {
133 width:15px;
133 width:15px;
134 height:15px;
134 height:15px;
135 display:block;
135 display:block;
136 float:left;
136 float:left;
137 margin:0 0 0 1px;
137 margin:0 0 0 1px;
138 padding:0;
138 padding:0;
139 }
139 }
140
140
141 div.options {
141 div.options {
142 clear:both;
142 clear:both;
143 overflow:hidden;
143 overflow:hidden;
144 position:absolute;
144 position:absolute;
145 background:#FFF;
145 background:#FFF;
146 margin:7px 0 0 162px;
146 margin:7px 0 0 162px;
147 padding:0;
147 padding:0;
148 }
148 }
149
149
150 div.options a {
150 div.options a {
151 height:1%;
151 height:1%;
152 display:block;
152 display:block;
153 text-decoration:none;
153 text-decoration:none;
154 margin:0;
154 margin:0;
155 padding:3px 8px;
155 padding:3px 8px;
156 }
156 }
157
157
158 .top-left-rounded-corner {
158 .top-left-rounded-corner {
159 -webkit-border-top-left-radius: 8px;
159 -webkit-border-top-left-radius: 8px;
160 -khtml-border-radius-topleft: 8px;
160 -khtml-border-radius-topleft: 8px;
161 -moz-border-radius-topleft: 8px;
161 -moz-border-radius-topleft: 8px;
162 border-top-left-radius: 8px;
162 border-top-left-radius: 8px;
163 }
163 }
164
164
165 .top-right-rounded-corner {
165 .top-right-rounded-corner {
166 -webkit-border-top-right-radius: 8px;
166 -webkit-border-top-right-radius: 8px;
167 -khtml-border-radius-topright: 8px;
167 -khtml-border-radius-topright: 8px;
168 -moz-border-radius-topright: 8px;
168 -moz-border-radius-topright: 8px;
169 border-top-right-radius: 8px;
169 border-top-right-radius: 8px;
170 }
170 }
171
171
172 .bottom-left-rounded-corner {
172 .bottom-left-rounded-corner {
173 -webkit-border-bottom-left-radius: 8px;
173 -webkit-border-bottom-left-radius: 8px;
174 -khtml-border-radius-bottomleft: 8px;
174 -khtml-border-radius-bottomleft: 8px;
175 -moz-border-radius-bottomleft: 8px;
175 -moz-border-radius-bottomleft: 8px;
176 border-bottom-left-radius: 8px;
176 border-bottom-left-radius: 8px;
177 }
177 }
178
178
179 .bottom-right-rounded-corner {
179 .bottom-right-rounded-corner {
180 -webkit-border-bottom-right-radius: 8px;
180 -webkit-border-bottom-right-radius: 8px;
181 -khtml-border-radius-bottomright: 8px;
181 -khtml-border-radius-bottomright: 8px;
182 -moz-border-radius-bottomright: 8px;
182 -moz-border-radius-bottomright: 8px;
183 border-bottom-right-radius: 8px;
183 border-bottom-right-radius: 8px;
184 }
184 }
185
185
186
186
187 #header {
187 #header {
188 margin:0;
188 margin:0;
189 padding:0 10px;
189 padding:0 10px;
190 }
190 }
191
191
192
192
193 #header ul#logged-user{
193 #header ul#logged-user{
194 margin-bottom:5px !important;
194 margin-bottom:5px !important;
195 -webkit-border-radius: 0px 0px 8px 8px;
195 -webkit-border-radius: 0px 0px 8px 8px;
196 -khtml-border-radius: 0px 0px 8px 8px;
196 -khtml-border-radius: 0px 0px 8px 8px;
197 -moz-border-radius: 0px 0px 8px 8px;
197 -moz-border-radius: 0px 0px 8px 8px;
198 border-radius: 0px 0px 8px 8px;
198 border-radius: 0px 0px 8px 8px;
199 height:37px;
199 height:37px;
200 background:url("../images/header_inner.png") repeat-x scroll 0 0 #003367;
200 background:url("../images/header_inner.png") repeat-x scroll 0 0 #003367;
201 box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
201 box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
202 }
202 }
203
203
204 #header ul#logged-user li {
204 #header ul#logged-user li {
205 list-style:none;
205 list-style:none;
206 float:left;
206 float:left;
207 margin:8px 0 0;
207 margin:8px 0 0;
208 padding:4px 12px;
208 padding:4px 12px;
209 border-left: 1px solid #316293;
209 border-left: 1px solid #316293;
210 }
210 }
211
211
212 #header ul#logged-user li.first {
212 #header ul#logged-user li.first {
213 border-left:none;
213 border-left:none;
214 margin:4px;
214 margin:4px;
215 }
215 }
216
216
217 #header ul#logged-user li.first div.gravatar {
217 #header ul#logged-user li.first div.gravatar {
218 margin-top:-2px;
218 margin-top:-2px;
219 }
219 }
220
220
221 #header ul#logged-user li.first div.account {
221 #header ul#logged-user li.first div.account {
222 padding-top:4px;
222 padding-top:4px;
223 float:left;
223 float:left;
224 }
224 }
225
225
226 #header ul#logged-user li.last {
226 #header ul#logged-user li.last {
227 border-right:none;
227 border-right:none;
228 }
228 }
229
229
230 #header ul#logged-user li a {
230 #header ul#logged-user li a {
231 color:#fff;
231 color:#fff;
232 font-weight:700;
232 font-weight:700;
233 text-decoration:none;
233 text-decoration:none;
234 }
234 }
235
235
236 #header ul#logged-user li a:hover {
236 #header ul#logged-user li a:hover {
237 text-decoration:underline;
237 text-decoration:underline;
238 }
238 }
239
239
240 #header ul#logged-user li.highlight a {
240 #header ul#logged-user li.highlight a {
241 color:#fff;
241 color:#fff;
242 }
242 }
243
243
244 #header ul#logged-user li.highlight a:hover {
244 #header ul#logged-user li.highlight a:hover {
245 color:#FFF;
245 color:#FFF;
246 }
246 }
247
247
248 #header #header-inner {
248 #header #header-inner {
249 height:40px;
249 min-height:40px;
250 clear:both;
250 clear:both;
251 position:relative;
251 position:relative;
252 background:#003367 url("../images/header_inner.png") repeat-x;
252 background:#003367 url("../images/header_inner.png") repeat-x;
253 margin:0;
253 margin:0;
254 padding:0;
254 padding:0;
255 display:block;
255 box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
256 box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
256 -webkit-border-radius: 4px 4px 4px 4px;
257 -webkit-border-radius: 4px 4px 4px 4px;
257 -khtml-border-radius: 4px 4px 4px 4px;
258 -khtml-border-radius: 4px 4px 4px 4px;
258 -moz-border-radius: 4px 4px 4px 4px;
259 -moz-border-radius: 4px 4px 4px 4px;
259 border-radius: 4px 4px 4px 4px;
260 border-radius: 4px 4px 4px 4px;
260 }
261 }
261
262
262 #header #header-inner #home a {
263 #header #header-inner #home a {
263 height:40px;
264 height:40px;
264 width:46px;
265 width:46px;
265 display:block;
266 display:block;
266 background:url("../images/button_home.png");
267 background:url("../images/button_home.png");
267 background-position:0 0;
268 background-position:0 0;
268 margin:0;
269 margin:0;
269 padding:0;
270 padding:0;
270 }
271 }
271
272
272 #header #header-inner #home a:hover {
273 #header #header-inner #home a:hover {
273 background-position:0 -40px;
274 background-position:0 -40px;
274 }
275 }
275
276 #header #header-inner #logo {
277 float: left;
278 position: absolute;
279 }
276 #header #header-inner #logo h1 {
280 #header #header-inner #logo h1 {
277 color:#FFF;
281 color:#FFF;
278 font-size:18px;
282 font-size:18px;
279 margin:10px 0 0 13px;
283 margin:10px 0 0 13px;
280 padding:0;
284 padding:0;
281 }
285 }
282
286
283 #header #header-inner #logo a {
287 #header #header-inner #logo a {
284 color:#fff;
288 color:#fff;
285 text-decoration:none;
289 text-decoration:none;
286 }
290 }
287
291
288 #header #header-inner #logo a:hover {
292 #header #header-inner #logo a:hover {
289 color:#bfe3ff;
293 color:#bfe3ff;
290 }
294 }
291
295
292 #header #header-inner #quick,#header #header-inner #quick ul {
296 #header #header-inner #quick,#header #header-inner #quick ul {
293 position:relative;
297 position:relative;
294 float:right;
298 float:right;
295 list-style-type:none;
299 list-style-type:none;
296 list-style-position:outside;
300 list-style-position:outside;
297 margin:6px 5px 0 0;
301 margin:6px 5px 0 0;
298 padding:0;
302 padding:0;
299 }
303 }
300
304
301 #header #header-inner #quick li {
305 #header #header-inner #quick li {
302 position:relative;
306 position:relative;
303 float:left;
307 float:left;
304 margin:0 5px 0 0;
308 margin:0 5px 0 0;
305 padding:0;
309 padding:0;
306 }
310 }
307
311
308 #header #header-inner #quick li a {
312 #header #header-inner #quick li a {
309 top:0;
313 top:0;
310 left:0;
314 left:0;
311 height:1%;
315 height:1%;
312 display:block;
316 display:block;
313 clear:both;
317 clear:both;
314 overflow:hidden;
318 overflow:hidden;
315 color:#FFF;
319 color:#FFF;
316 font-weight:700;
320 font-weight:700;
317 text-decoration:none;
321 text-decoration:none;
318 background:#369;
322 background:#369;
319 padding:0;
323 padding:0;
320 -webkit-border-radius: 4px 4px 4px 4px;
324 -webkit-border-radius: 4px 4px 4px 4px;
321 -khtml-border-radius: 4px 4px 4px 4px;
325 -khtml-border-radius: 4px 4px 4px 4px;
322 -moz-border-radius: 4px 4px 4px 4px;
326 -moz-border-radius: 4px 4px 4px 4px;
323 border-radius: 4px 4px 4px 4px;
327 border-radius: 4px 4px 4px 4px;
324 }
328 }
325
329
326 #header #header-inner #quick li span.short {
330 #header #header-inner #quick li span.short {
327 padding:9px 6px 8px 6px;
331 padding:9px 6px 8px 6px;
328 }
332 }
329
333
330 #header #header-inner #quick li span {
334 #header #header-inner #quick li span {
331 top:0;
335 top:0;
332 right:0;
336 right:0;
333 height:1%;
337 height:1%;
334 display:block;
338 display:block;
335 float:left;
339 float:left;
336 border-left:1px solid #3f6f9f;
340 border-left:1px solid #3f6f9f;
337 margin:0;
341 margin:0;
338 padding:10px 12px 8px 10px;
342 padding:10px 12px 8px 10px;
339 }
343 }
340
344
341 #header #header-inner #quick li span.normal {
345 #header #header-inner #quick li span.normal {
342 border:none;
346 border:none;
343 padding:10px 12px 8px;
347 padding:10px 12px 8px;
344 }
348 }
345
349
346 #header #header-inner #quick li span.icon {
350 #header #header-inner #quick li span.icon {
347 top:0;
351 top:0;
348 left:0;
352 left:0;
349 border-left:none;
353 border-left:none;
350 border-right:1px solid #2e5c89;
354 border-right:1px solid #2e5c89;
351 padding:8px 8px 4px;
355 padding:8px 6px 4px;
352 }
356 }
353
357
354 #header #header-inner #quick li span.icon_short {
358 #header #header-inner #quick li span.icon_short {
355 top:0;
359 top:0;
356 left:0;
360 left:0;
357 border-left:none;
361 border-left:none;
358 border-right:1px solid #2e5c89;
362 border-right:1px solid #2e5c89;
359 padding:9px 4px 4px;
363 padding:8px 6px 4px;
364 }
365 #header #header-inner #quick li span.icon img, #header #header-inner #quick li span.icon_short img {
366 margin: 0px -2px 0px 0px;
360 }
367 }
361
368
362 #header #header-inner #quick li a:hover {
369 #header #header-inner #quick li a:hover {
363 background:#4e4e4e no-repeat top left;
370 background:#4e4e4e no-repeat top left;
364 }
371 }
365
372
366 #header #header-inner #quick li a:hover span {
373 #header #header-inner #quick li a:hover span {
367 border-left:1px solid #545454;
374 border-left:1px solid #545454;
368 }
375 }
369
376
370 #header #header-inner #quick li a:hover span.icon,#header #header-inner #quick li a:hover span.icon_short {
377 #header #header-inner #quick li a:hover span.icon,#header #header-inner #quick li a:hover span.icon_short {
371 border-left:none;
378 border-left:none;
372 border-right:1px solid #464646;
379 border-right:1px solid #464646;
373 }
380 }
374
381
375 #header #header-inner #quick ul {
382 #header #header-inner #quick ul {
376 top:29px;
383 top:29px;
377 right:0;
384 right:0;
378 min-width:200px;
385 min-width:200px;
379 display:none;
386 display:none;
380 position:absolute;
387 position:absolute;
381 background:#FFF;
388 background:#FFF;
382 border:1px solid #666;
389 border:1px solid #666;
383 border-top:1px solid #003367;
390 border-top:1px solid #003367;
384 z-index:100;
391 z-index:100;
385 margin:0;
392 margin:0;
386 padding:0;
393 padding:0;
387 }
394 }
388
395
389 #header #header-inner #quick ul.repo_switcher {
396 #header #header-inner #quick ul.repo_switcher {
390 max-height:275px;
397 max-height:275px;
391 overflow-x:hidden;
398 overflow-x:hidden;
392 overflow-y:auto;
399 overflow-y:auto;
393 }
400 }
394 #header #header-inner #quick ul.repo_switcher li.qfilter_rs {
401 #header #header-inner #quick ul.repo_switcher li.qfilter_rs {
395 float:none;
402 float:none;
396 margin:0;
403 margin:0;
397 border-bottom:2px solid #003367;
404 border-bottom:2px solid #003367;
398 }
405 }
399
406
400
407
401 #header #header-inner #quick .repo_switcher_type{
408 #header #header-inner #quick .repo_switcher_type{
402 position:absolute;
409 position:absolute;
403 left:0;
410 left:0;
404 top:9px;
411 top:9px;
405
412
406 }
413 }
407 #header #header-inner #quick li ul li {
414 #header #header-inner #quick li ul li {
408 border-bottom:1px solid #ddd;
415 border-bottom:1px solid #ddd;
409 }
416 }
410
417
411 #header #header-inner #quick li ul li a {
418 #header #header-inner #quick li ul li a {
412 width:182px;
419 width:182px;
413 height:auto;
420 height:auto;
414 display:block;
421 display:block;
415 float:left;
422 float:left;
416 background:#FFF;
423 background:#FFF;
417 color:#003367;
424 color:#003367;
418 font-weight:400;
425 font-weight:400;
419 margin:0;
426 margin:0;
420 padding:7px 9px;
427 padding:7px 9px;
421 }
428 }
422
429
423 #header #header-inner #quick li ul li a:hover {
430 #header #header-inner #quick li ul li a:hover {
424 color:#000;
431 color:#000;
425 background:#FFF;
432 background:#FFF;
426 }
433 }
427
434
428 #header #header-inner #quick ul ul {
435 #header #header-inner #quick ul ul {
429 top:auto;
436 top:auto;
430 }
437 }
431
438
432 #header #header-inner #quick li ul ul {
439 #header #header-inner #quick li ul ul {
433 right:200px;
440 right:200px;
434 max-height:275px;
441 max-height:275px;
435 overflow:auto;
442 overflow:auto;
436 overflow-x:hidden;
443 overflow-x:hidden;
437 white-space:normal;
444 white-space:normal;
438 }
445 }
439
446
440 #header #header-inner #quick li ul li a.journal,#header #header-inner #quick li ul li a.journal:hover {
447 #header #header-inner #quick li ul li a.journal,#header #header-inner #quick li ul li a.journal:hover {
441 background:url("../images/icons/book.png") no-repeat scroll 4px 9px #FFF;
448 background:url("../images/icons/book.png") no-repeat scroll 4px 9px #FFF;
442 width:167px;
449 width:167px;
443 margin:0;
450 margin:0;
444 padding:12px 9px 7px 24px;
451 padding:12px 9px 7px 24px;
445 }
452 }
446
453
447 #header #header-inner #quick li ul li a.private_repo,#header #header-inner #quick li ul li a.private_repo:hover {
454 #header #header-inner #quick li ul li a.private_repo,#header #header-inner #quick li ul li a.private_repo:hover {
448 background:url("../images/icons/lock.png") no-repeat scroll 4px 9px #FFF;
455 background:url("../images/icons/lock.png") no-repeat scroll 4px 9px #FFF;
449 min-width:167px;
456 min-width:167px;
450 margin:0;
457 margin:0;
451 padding:12px 9px 7px 24px;
458 padding:12px 9px 7px 24px;
452 }
459 }
453
460
454 #header #header-inner #quick li ul li a.public_repo,#header #header-inner #quick li ul li a.public_repo:hover {
461 #header #header-inner #quick li ul li a.public_repo,#header #header-inner #quick li ul li a.public_repo:hover {
455 background:url("../images/icons/lock_open.png") no-repeat scroll 4px 9px #FFF;
462 background:url("../images/icons/lock_open.png") no-repeat scroll 4px 9px #FFF;
456 min-width:167px;
463 min-width:167px;
457 margin:0;
464 margin:0;
458 padding:12px 9px 7px 24px;
465 padding:12px 9px 7px 24px;
459 }
466 }
460
467
461 #header #header-inner #quick li ul li a.hg,#header #header-inner #quick li ul li a.hg:hover {
468 #header #header-inner #quick li ul li a.hg,#header #header-inner #quick li ul li a.hg:hover {
462 background:url("../images/icons/hgicon.png") no-repeat scroll 4px 9px #FFF;
469 background:url("../images/icons/hgicon.png") no-repeat scroll 4px 9px #FFF;
463 min-width:167px;
470 min-width:167px;
464 margin:0 0 0 14px;
471 margin:0 0 0 14px;
465 padding:12px 9px 7px 24px;
472 padding:12px 9px 7px 24px;
466 }
473 }
467
474
468 #header #header-inner #quick li ul li a.git,#header #header-inner #quick li ul li a.git:hover {
475 #header #header-inner #quick li ul li a.git,#header #header-inner #quick li ul li a.git:hover {
469 background:url("../images/icons/giticon.png") no-repeat scroll 4px 9px #FFF;
476 background:url("../images/icons/giticon.png") no-repeat scroll 4px 9px #FFF;
470 min-width:167px;
477 min-width:167px;
471 margin:0 0 0 14px;
478 margin:0 0 0 14px;
472 padding:12px 9px 7px 24px;
479 padding:12px 9px 7px 24px;
473 }
480 }
474
481
475 #header #header-inner #quick li ul li a.repos,#header #header-inner #quick li ul li a.repos:hover {
482 #header #header-inner #quick li ul li a.repos,#header #header-inner #quick li ul li a.repos:hover {
476 background:url("../images/icons/database_edit.png") no-repeat scroll 4px 9px #FFF;
483 background:url("../images/icons/database_edit.png") no-repeat scroll 4px 9px #FFF;
477 width:167px;
484 width:167px;
478 margin:0;
485 margin:0;
479 padding:12px 9px 7px 24px;
486 padding:12px 9px 7px 24px;
480 }
487 }
481
488
482 #header #header-inner #quick li ul li a.repos_groups,#header #header-inner #quick li ul li a.repos_groups:hover {
489 #header #header-inner #quick li ul li a.repos_groups,#header #header-inner #quick li ul li a.repos_groups:hover {
483 background:url("../images/icons/database_link.png") no-repeat scroll 4px 9px #FFF;
490 background:url("../images/icons/database_link.png") no-repeat scroll 4px 9px #FFF;
484 width:167px;
491 width:167px;
485 margin:0;
492 margin:0;
486 padding:12px 9px 7px 24px;
493 padding:12px 9px 7px 24px;
487 }
494 }
488
495
489 #header #header-inner #quick li ul li a.users,#header #header-inner #quick li ul li a.users:hover {
496 #header #header-inner #quick li ul li a.users,#header #header-inner #quick li ul li a.users:hover {
490 background:#FFF url("../images/icons/user_edit.png") no-repeat 4px 9px;
497 background:#FFF url("../images/icons/user_edit.png") no-repeat 4px 9px;
491 width:167px;
498 width:167px;
492 margin:0;
499 margin:0;
493 padding:12px 9px 7px 24px;
500 padding:12px 9px 7px 24px;
494 }
501 }
495
502
496 #header #header-inner #quick li ul li a.groups,#header #header-inner #quick li ul li a.groups:hover {
503 #header #header-inner #quick li ul li a.groups,#header #header-inner #quick li ul li a.groups:hover {
497 background:#FFF url("../images/icons/group_edit.png") no-repeat 4px 9px;
504 background:#FFF url("../images/icons/group_edit.png") no-repeat 4px 9px;
498 width:167px;
505 width:167px;
499 margin:0;
506 margin:0;
500 padding:12px 9px 7px 24px;
507 padding:12px 9px 7px 24px;
501 }
508 }
502
509
503 #header #header-inner #quick li ul li a.settings,#header #header-inner #quick li ul li a.settings:hover {
510 #header #header-inner #quick li ul li a.settings,#header #header-inner #quick li ul li a.settings:hover {
504 background:#FFF url("../images/icons/cog.png") no-repeat 4px 9px;
511 background:#FFF url("../images/icons/cog.png") no-repeat 4px 9px;
505 width:167px;
512 width:167px;
506 margin:0;
513 margin:0;
507 padding:12px 9px 7px 24px;
514 padding:12px 9px 7px 24px;
508 }
515 }
509
516
510 #header #header-inner #quick li ul li a.permissions,#header #header-inner #quick li ul li a.permissions:hover {
517 #header #header-inner #quick li ul li a.permissions,#header #header-inner #quick li ul li a.permissions:hover {
511 background:#FFF url("../images/icons/key.png") no-repeat 4px 9px;
518 background:#FFF url("../images/icons/key.png") no-repeat 4px 9px;
512 width:167px;
519 width:167px;
513 margin:0;
520 margin:0;
514 padding:12px 9px 7px 24px;
521 padding:12px 9px 7px 24px;
515 }
522 }
516
523
517 #header #header-inner #quick li ul li a.ldap,#header #header-inner #quick li ul li a.ldap:hover {
524 #header #header-inner #quick li ul li a.ldap,#header #header-inner #quick li ul li a.ldap:hover {
518 background:#FFF url("../images/icons/server_key.png") no-repeat 4px 9px;
525 background:#FFF url("../images/icons/server_key.png") no-repeat 4px 9px;
519 width:167px;
526 width:167px;
520 margin:0;
527 margin:0;
521 padding:12px 9px 7px 24px;
528 padding:12px 9px 7px 24px;
522 }
529 }
523
530
524 #header #header-inner #quick li ul li a.fork,#header #header-inner #quick li ul li a.fork:hover {
531 #header #header-inner #quick li ul li a.fork,#header #header-inner #quick li ul li a.fork:hover {
525 background:#FFF url("../images/icons/arrow_divide.png") no-repeat 4px 9px;
532 background:#FFF url("../images/icons/arrow_divide.png") no-repeat 4px 9px;
526 width:167px;
533 width:167px;
527 margin:0;
534 margin:0;
528 padding:12px 9px 7px 24px;
535 padding:12px 9px 7px 24px;
529 }
536 }
530
537
531 #header #header-inner #quick li ul li a.search,#header #header-inner #quick li ul li a.search:hover {
538 #header #header-inner #quick li ul li a.search,#header #header-inner #quick li ul li a.search:hover {
532 background:#FFF url("../images/icons/search_16.png") no-repeat 4px 9px;
539 background:#FFF url("../images/icons/search_16.png") no-repeat 4px 9px;
533 width:167px;
540 width:167px;
534 margin:0;
541 margin:0;
535 padding:12px 9px 7px 24px;
542 padding:12px 9px 7px 24px;
536 }
543 }
537
544
538 #header #header-inner #quick li ul li a.delete,#header #header-inner #quick li ul li a.delete:hover {
545 #header #header-inner #quick li ul li a.delete,#header #header-inner #quick li ul li a.delete:hover {
539 background:#FFF url("../images/icons/delete.png") no-repeat 4px 9px;
546 background:#FFF url("../images/icons/delete.png") no-repeat 4px 9px;
540 width:167px;
547 width:167px;
541 margin:0;
548 margin:0;
542 padding:12px 9px 7px 24px;
549 padding:12px 9px 7px 24px;
543 }
550 }
544
551
545 #header #header-inner #quick li ul li a.branches,#header #header-inner #quick li ul li a.branches:hover {
552 #header #header-inner #quick li ul li a.branches,#header #header-inner #quick li ul li a.branches:hover {
546 background:#FFF url("../images/icons/arrow_branch.png") no-repeat 4px 9px;
553 background:#FFF url("../images/icons/arrow_branch.png") no-repeat 4px 9px;
547 width:167px;
554 width:167px;
548 margin:0;
555 margin:0;
549 padding:12px 9px 7px 24px;
556 padding:12px 9px 7px 24px;
550 }
557 }
551
558
552 #header #header-inner #quick li ul li a.tags,#header #header-inner #quick li ul li a.tags:hover {
559 #header #header-inner #quick li ul li a.tags,#header #header-inner #quick li ul li a.tags:hover {
553 background:#FFF url("../images/icons/tag_blue.png") no-repeat 4px 9px;
560 background:#FFF url("../images/icons/tag_blue.png") no-repeat 4px 9px;
554 width:167px;
561 width:167px;
555 margin:0;
562 margin:0;
556 padding:12px 9px 7px 24px;
563 padding:12px 9px 7px 24px;
557 }
564 }
558
565
559 #header #header-inner #quick li ul li a.admin,#header #header-inner #quick li ul li a.admin:hover {
566 #header #header-inner #quick li ul li a.admin,#header #header-inner #quick li ul li a.admin:hover {
560 background:#FFF url("../images/icons/cog_edit.png") no-repeat 4px 9px;
567 background:#FFF url("../images/icons/cog_edit.png") no-repeat 4px 9px;
561 width:167px;
568 width:167px;
562 margin:0;
569 margin:0;
563 padding:12px 9px 7px 24px;
570 padding:12px 9px 7px 24px;
564 }
571 }
565
572
566
573
574 .groups_breadcrumbs a {
575 color: #fff;
576 }
577 .groups_breadcrumbs a:hover {
578 color: #bfe3ff;
579 text-decoration: none;
580 }
581
567 .quick_repo_menu{
582 .quick_repo_menu{
568 background: #FFF url("../images/vertical-indicator.png") 8px 50% no-repeat !important;
583 background: #FFF url("../images/vertical-indicator.png") 8px 50% no-repeat !important;
569 cursor: pointer;
584 cursor: pointer;
570 width: 8px;
585 width: 8px;
571 }
586 }
572 .quick_repo_menu.active{
587 .quick_repo_menu.active{
573 background: #FFF url("../images/horizontal-indicator.png") 4px 50% no-repeat !important;
588 background: #FFF url("../images/horizontal-indicator.png") 4px 50% no-repeat !important;
574 cursor: pointer;
589 cursor: pointer;
575 }
590 }
576 .quick_repo_menu .menu_items{
591 .quick_repo_menu .menu_items{
577 margin-top:6px;
592 margin-top:6px;
578 width:150px;
593 width:150px;
579 position: absolute;
594 position: absolute;
580 background-color:#FFF;
595 background-color:#FFF;
581 background: none repeat scroll 0 0 #FFFFFF;
596 background: none repeat scroll 0 0 #FFFFFF;
582 border-color: #003367 #666666 #666666;
597 border-color: #003367 #666666 #666666;
583 border-right: 1px solid #666666;
598 border-right: 1px solid #666666;
584 border-style: solid;
599 border-style: solid;
585 border-width: 1px;
600 border-width: 1px;
586 box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
601 box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
587 }
602 }
588 .quick_repo_menu .menu_items li{
603 .quick_repo_menu .menu_items li{
589 padding:0 !important;
604 padding:0 !important;
590 }
605 }
591 .quick_repo_menu .menu_items a{
606 .quick_repo_menu .menu_items a{
592 display: block;
607 display: block;
593 padding: 4px 12px 4px 8px;
608 padding: 4px 12px 4px 8px;
594 }
609 }
595 .quick_repo_menu .menu_items a:hover{
610 .quick_repo_menu .menu_items a:hover{
596 background-color: #EEE;
611 background-color: #EEE;
597 text-decoration: none;
612 text-decoration: none;
598
613
599 }
614 }
600 .quick_repo_menu .menu_items .icon img{
615 .quick_repo_menu .menu_items .icon img{
601 margin-bottom:-2px;
616 margin-bottom:-2px;
602 }
617 }
603 .quick_repo_menu .menu_items.hidden{
618 .quick_repo_menu .menu_items.hidden{
604 display: none;
619 display: none;
605 }
620 }
606
621
607 #content #left {
622 #content #left {
608 left:0;
623 left:0;
609 width:280px;
624 width:280px;
610 position:absolute;
625 position:absolute;
611 }
626 }
612
627
613 #content #right {
628 #content #right {
614 margin:0 60px 10px 290px;
629 margin:0 60px 10px 290px;
615 }
630 }
616
631
617 #content div.box {
632 #content div.box {
618 clear:both;
633 clear:both;
619 overflow:hidden;
634 overflow:hidden;
620 background:#fff;
635 background:#fff;
621 margin:0 0 10px;
636 margin:0 0 10px;
622 padding:0 0 10px;
637 padding:0 0 10px;
623 -webkit-border-radius: 4px 4px 4px 4px;
638 -webkit-border-radius: 4px 4px 4px 4px;
624 -khtml-border-radius: 4px 4px 4px 4px;
639 -khtml-border-radius: 4px 4px 4px 4px;
625 -moz-border-radius: 4px 4px 4px 4px;
640 -moz-border-radius: 4px 4px 4px 4px;
626 border-radius: 4px 4px 4px 4px;
641 border-radius: 4px 4px 4px 4px;
627 box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
642 box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
628
643
629 }
644 }
630
645
631 #content div.box-left {
646 #content div.box-left {
632 width:49%;
647 width:49%;
633 clear:none;
648 clear:none;
634 float:left;
649 float:left;
635 margin:0 0 10px;
650 margin:0 0 10px;
636 }
651 }
637
652
638 #content div.box-right {
653 #content div.box-right {
639 width:49%;
654 width:49%;
640 clear:none;
655 clear:none;
641 float:right;
656 float:right;
642 margin:0 0 10px;
657 margin:0 0 10px;
643 }
658 }
644
659
645 #content div.box div.title {
660 #content div.box div.title {
646 clear:both;
661 clear:both;
647 overflow:hidden;
662 overflow:hidden;
648 background:#369 url("../images/header_inner.png") repeat-x;
663 background:#369 url("../images/header_inner.png") repeat-x;
649 margin:0 0 20px;
664 margin:0 0 20px;
650 padding:0;
665 padding:0;
651 }
666 }
652
667
653 #content div.box div.title h5 {
668 #content div.box div.title h5 {
654 float:left;
669 float:left;
655 border:none;
670 border:none;
656 color:#fff;
671 color:#fff;
657 text-transform:uppercase;
672 text-transform:uppercase;
658 margin:0;
673 margin:0;
659 padding:11px 0 11px 10px;
674 padding:11px 0 11px 10px;
660 }
675 }
661
676
662 #content div.box div.title ul.links li {
677 #content div.box div.title ul.links li {
663 list-style:none;
678 list-style:none;
664 float:left;
679 float:left;
665 margin:0;
680 margin:0;
666 padding:0;
681 padding:0;
667 }
682 }
668
683
669 #content div.box div.title ul.links li a {
684 #content div.box div.title ul.links li a {
670 border-left: 1px solid #316293;
685 border-left: 1px solid #316293;
671 color: #FFFFFF;
686 color: #FFFFFF;
672 display: block;
687 display: block;
673 float: left;
688 float: left;
674 font-size: 13px;
689 font-size: 13px;
675 font-weight: 700;
690 font-weight: 700;
676 height: 1%;
691 height: 1%;
677 margin: 0;
692 margin: 0;
678 padding: 11px 22px 12px;
693 padding: 11px 22px 12px;
679 text-decoration: none;
694 text-decoration: none;
680 }
695 }
681
696
682 #content div.box h1,#content div.box h2,#content div.box h3,#content div.box h4,#content div.box h5,#content div.box h6 {
697 #content div.box h1,#content div.box h2,#content div.box h3,#content div.box h4,#content div.box h5,#content div.box h6 {
683 clear:both;
698 clear:both;
684 overflow:hidden;
699 overflow:hidden;
685 border-bottom:1px solid #DDD;
700 border-bottom:1px solid #DDD;
686 margin:10px 20px;
701 margin:10px 20px;
687 padding:0 0 15px;
702 padding:0 0 15px;
688 }
703 }
689
704
690 #content div.box p {
705 #content div.box p {
691 color:#5f5f5f;
706 color:#5f5f5f;
692 font-size:12px;
707 font-size:12px;
693 line-height:150%;
708 line-height:150%;
694 margin:0 24px 10px;
709 margin:0 24px 10px;
695 padding:0;
710 padding:0;
696 }
711 }
697
712
698 #content div.box blockquote {
713 #content div.box blockquote {
699 border-left:4px solid #DDD;
714 border-left:4px solid #DDD;
700 color:#5f5f5f;
715 color:#5f5f5f;
701 font-size:11px;
716 font-size:11px;
702 line-height:150%;
717 line-height:150%;
703 margin:0 34px;
718 margin:0 34px;
704 padding:0 0 0 14px;
719 padding:0 0 0 14px;
705 }
720 }
706
721
707 #content div.box blockquote p {
722 #content div.box blockquote p {
708 margin:10px 0;
723 margin:10px 0;
709 padding:0;
724 padding:0;
710 }
725 }
711
726
712 #content div.box dl {
727 #content div.box dl {
713 margin:10px 24px;
728 margin:10px 24px;
714 }
729 }
715
730
716 #content div.box dt {
731 #content div.box dt {
717 font-size:12px;
732 font-size:12px;
718 margin:0;
733 margin:0;
719 }
734 }
720
735
721 #content div.box dd {
736 #content div.box dd {
722 font-size:12px;
737 font-size:12px;
723 margin:0;
738 margin:0;
724 padding:8px 0 8px 15px;
739 padding:8px 0 8px 15px;
725 }
740 }
726
741
727 #content div.box li {
742 #content div.box li {
728 font-size:12px;
743 font-size:12px;
729 padding:4px 0;
744 padding:4px 0;
730 }
745 }
731
746
732 #content div.box ul.disc,#content div.box ul.circle {
747 #content div.box ul.disc,#content div.box ul.circle {
733 margin:10px 24px 10px 38px;
748 margin:10px 24px 10px 38px;
734 }
749 }
735
750
736 #content div.box ul.square {
751 #content div.box ul.square {
737 margin:10px 24px 10px 40px;
752 margin:10px 24px 10px 40px;
738 }
753 }
739
754
740 #content div.box img.left {
755 #content div.box img.left {
741 border:none;
756 border:none;
742 float:left;
757 float:left;
743 margin:10px 10px 10px 0;
758 margin:10px 10px 10px 0;
744 }
759 }
745
760
746 #content div.box img.right {
761 #content div.box img.right {
747 border:none;
762 border:none;
748 float:right;
763 float:right;
749 margin:10px 0 10px 10px;
764 margin:10px 0 10px 10px;
750 }
765 }
751
766
752 #content div.box div.messages {
767 #content div.box div.messages {
753 clear:both;
768 clear:both;
754 overflow:hidden;
769 overflow:hidden;
755 margin:0 20px;
770 margin:0 20px;
756 padding:0;
771 padding:0;
757 }
772 }
758
773
759 #content div.box div.message {
774 #content div.box div.message {
760 clear:both;
775 clear:both;
761 overflow:hidden;
776 overflow:hidden;
762 margin:0;
777 margin:0;
763 padding:10px 0;
778 padding:10px 0;
764 }
779 }
765
780
766 #content div.box div.message a {
781 #content div.box div.message a {
767 font-weight:400 !important;
782 font-weight:400 !important;
768 }
783 }
769
784
770 #content div.box div.message div.image {
785 #content div.box div.message div.image {
771 float:left;
786 float:left;
772 margin:9px 0 0 5px;
787 margin:9px 0 0 5px;
773 padding:6px;
788 padding:6px;
774 }
789 }
775
790
776 #content div.box div.message div.image img {
791 #content div.box div.message div.image img {
777 vertical-align:middle;
792 vertical-align:middle;
778 margin:0;
793 margin:0;
779 }
794 }
780
795
781 #content div.box div.message div.text {
796 #content div.box div.message div.text {
782 float:left;
797 float:left;
783 margin:0;
798 margin:0;
784 padding:9px 6px;
799 padding:9px 6px;
785 }
800 }
786
801
787 #content div.box div.message div.dismiss a {
802 #content div.box div.message div.dismiss a {
788 height:16px;
803 height:16px;
789 width:16px;
804 width:16px;
790 display:block;
805 display:block;
791 background:url("../images/icons/cross.png") no-repeat;
806 background:url("../images/icons/cross.png") no-repeat;
792 margin:15px 14px 0 0;
807 margin:15px 14px 0 0;
793 padding:0;
808 padding:0;
794 }
809 }
795
810
796 #content div.box div.message div.text h1,#content div.box div.message div.text h2,#content div.box div.message div.text h3,#content div.box div.message div.text h4,#content div.box div.message div.text h5,#content div.box div.message div.text h6 {
811 #content div.box div.message div.text h1,#content div.box div.message div.text h2,#content div.box div.message div.text h3,#content div.box div.message div.text h4,#content div.box div.message div.text h5,#content div.box div.message div.text h6 {
797 border:none;
812 border:none;
798 margin:0;
813 margin:0;
799 padding:0;
814 padding:0;
800 }
815 }
801
816
802 #content div.box div.message div.text span {
817 #content div.box div.message div.text span {
803 height:1%;
818 height:1%;
804 display:block;
819 display:block;
805 margin:0;
820 margin:0;
806 padding:5px 0 0;
821 padding:5px 0 0;
807 }
822 }
808
823
809 #content div.box div.message-error {
824 #content div.box div.message-error {
810 height:1%;
825 height:1%;
811 clear:both;
826 clear:both;
812 overflow:hidden;
827 overflow:hidden;
813 background:#FBE3E4;
828 background:#FBE3E4;
814 border:1px solid #FBC2C4;
829 border:1px solid #FBC2C4;
815 color:#860006;
830 color:#860006;
816 }
831 }
817
832
818 #content div.box div.message-error h6 {
833 #content div.box div.message-error h6 {
819 color:#860006;
834 color:#860006;
820 }
835 }
821
836
822 #content div.box div.message-warning {
837 #content div.box div.message-warning {
823 height:1%;
838 height:1%;
824 clear:both;
839 clear:both;
825 overflow:hidden;
840 overflow:hidden;
826 background:#FFF6BF;
841 background:#FFF6BF;
827 border:1px solid #FFD324;
842 border:1px solid #FFD324;
828 color:#5f5200;
843 color:#5f5200;
829 }
844 }
830
845
831 #content div.box div.message-warning h6 {
846 #content div.box div.message-warning h6 {
832 color:#5f5200;
847 color:#5f5200;
833 }
848 }
834
849
835 #content div.box div.message-notice {
850 #content div.box div.message-notice {
836 height:1%;
851 height:1%;
837 clear:both;
852 clear:both;
838 overflow:hidden;
853 overflow:hidden;
839 background:#8FBDE0;
854 background:#8FBDE0;
840 border:1px solid #6BACDE;
855 border:1px solid #6BACDE;
841 color:#003863;
856 color:#003863;
842 }
857 }
843
858
844 #content div.box div.message-notice h6 {
859 #content div.box div.message-notice h6 {
845 color:#003863;
860 color:#003863;
846 }
861 }
847
862
848 #content div.box div.message-success {
863 #content div.box div.message-success {
849 height:1%;
864 height:1%;
850 clear:both;
865 clear:both;
851 overflow:hidden;
866 overflow:hidden;
852 background:#E6EFC2;
867 background:#E6EFC2;
853 border:1px solid #C6D880;
868 border:1px solid #C6D880;
854 color:#4e6100;
869 color:#4e6100;
855 }
870 }
856
871
857 #content div.box div.message-success h6 {
872 #content div.box div.message-success h6 {
858 color:#4e6100;
873 color:#4e6100;
859 }
874 }
860
875
861 #content div.box div.form div.fields div.field {
876 #content div.box div.form div.fields div.field {
862 height:1%;
877 height:1%;
863 border-bottom:1px solid #DDD;
878 border-bottom:1px solid #DDD;
864 clear:both;
879 clear:both;
865 margin:0;
880 margin:0;
866 padding:10px 0;
881 padding:10px 0;
867 }
882 }
868
883
869 #content div.box div.form div.fields div.field-first {
884 #content div.box div.form div.fields div.field-first {
870 padding:0 0 10px;
885 padding:0 0 10px;
871 }
886 }
872
887
873 #content div.box div.form div.fields div.field-noborder {
888 #content div.box div.form div.fields div.field-noborder {
874 border-bottom:0 !important;
889 border-bottom:0 !important;
875 }
890 }
876
891
877 #content div.box div.form div.fields div.field span.error-message {
892 #content div.box div.form div.fields div.field span.error-message {
878 height:1%;
893 height:1%;
879 display:inline-block;
894 display:inline-block;
880 color:red;
895 color:red;
881 margin:8px 0 0 4px;
896 margin:8px 0 0 4px;
882 padding:0;
897 padding:0;
883 }
898 }
884
899
885 #content div.box div.form div.fields div.field span.success {
900 #content div.box div.form div.fields div.field span.success {
886 height:1%;
901 height:1%;
887 display:block;
902 display:block;
888 color:#316309;
903 color:#316309;
889 margin:8px 0 0;
904 margin:8px 0 0;
890 padding:0;
905 padding:0;
891 }
906 }
892
907
893 #content div.box div.form div.fields div.field div.label {
908 #content div.box div.form div.fields div.field div.label {
894 left:70px;
909 left:70px;
895 width:155px;
910 width:155px;
896 position:absolute;
911 position:absolute;
897 margin:0;
912 margin:0;
898 padding:8px 0 0 5px;
913 padding:8px 0 0 5px;
899 }
914 }
900
915
901 #content div.box-left div.form div.fields div.field div.label,#content div.box-right div.form div.fields div.field div.label {
916 #content div.box-left div.form div.fields div.field div.label,#content div.box-right div.form div.fields div.field div.label {
902 clear:both;
917 clear:both;
903 overflow:hidden;
918 overflow:hidden;
904 left:0;
919 left:0;
905 width:auto;
920 width:auto;
906 position:relative;
921 position:relative;
907 margin:0;
922 margin:0;
908 padding:0 0 8px;
923 padding:0 0 8px;
909 }
924 }
910
925
911 #content div.box div.form div.fields div.field div.label-select {
926 #content div.box div.form div.fields div.field div.label-select {
912 padding:5px 0 0 5px;
927 padding:5px 0 0 5px;
913 }
928 }
914
929
915 #content div.box-left div.form div.fields div.field div.label-select,#content div.box-right div.form div.fields div.field div.label-select {
930 #content div.box-left div.form div.fields div.field div.label-select,#content div.box-right div.form div.fields div.field div.label-select {
916 padding:0 0 8px;
931 padding:0 0 8px;
917 }
932 }
918
933
919 #content div.box-left div.form div.fields div.field div.label-textarea,#content div.box-right div.form div.fields div.field div.label-textarea {
934 #content div.box-left div.form div.fields div.field div.label-textarea,#content div.box-right div.form div.fields div.field div.label-textarea {
920 padding:0 0 8px !important;
935 padding:0 0 8px !important;
921 }
936 }
922
937
923 #content div.box div.form div.fields div.field div.label label, div.label label{
938 #content div.box div.form div.fields div.field div.label label, div.label label{
924 color:#393939;
939 color:#393939;
925 font-weight:700;
940 font-weight:700;
926 }
941 }
927
942
928 #content div.box div.form div.fields div.field div.input {
943 #content div.box div.form div.fields div.field div.input {
929 margin:0 0 0 200px;
944 margin:0 0 0 200px;
930 }
945 }
931 #content div.box div.form div.fields div.field div.file {
946 #content div.box div.form div.fields div.field div.file {
932 margin:0 0 0 200px;
947 margin:0 0 0 200px;
933 }
948 }
934 #content div.box-left div.form div.fields div.field div.input,#content div.box-right div.form div.fields div.field div.input {
949 #content div.box-left div.form div.fields div.field div.input,#content div.box-right div.form div.fields div.field div.input {
935 margin:0 0 0 0px;
950 margin:0 0 0 0px;
936 }
951 }
937
952
938 #content div.box div.form div.fields div.field div.input input {
953 #content div.box div.form div.fields div.field div.input input {
939 background:#FFF;
954 background:#FFF;
940 border-top:1px solid #b3b3b3;
955 border-top:1px solid #b3b3b3;
941 border-left:1px solid #b3b3b3;
956 border-left:1px solid #b3b3b3;
942 border-right:1px solid #eaeaea;
957 border-right:1px solid #eaeaea;
943 border-bottom:1px solid #eaeaea;
958 border-bottom:1px solid #eaeaea;
944 color:#000;
959 color:#000;
945 font-family:Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
960 font-family:Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
946 font-size:11px;
961 font-size:11px;
947 margin:0;
962 margin:0;
948 padding:7px 7px 6px;
963 padding:7px 7px 6px;
949 }
964 }
950
965
951 #content div.box div.form div.fields div.field div.file input {
966 #content div.box div.form div.fields div.field div.file input {
952 background: none repeat scroll 0 0 #FFFFFF;
967 background: none repeat scroll 0 0 #FFFFFF;
953 border-color: #B3B3B3 #EAEAEA #EAEAEA #B3B3B3;
968 border-color: #B3B3B3 #EAEAEA #EAEAEA #B3B3B3;
954 border-style: solid;
969 border-style: solid;
955 border-width: 1px;
970 border-width: 1px;
956 color: #000000;
971 color: #000000;
957 font-family: Lucida Grande,Verdana,Lucida Sans Regular,Lucida Sans Unicode,Arial,sans-serif;
972 font-family: Lucida Grande,Verdana,Lucida Sans Regular,Lucida Sans Unicode,Arial,sans-serif;
958 font-size: 11px;
973 font-size: 11px;
959 margin: 0;
974 margin: 0;
960 padding: 7px 7px 6px;
975 padding: 7px 7px 6px;
961 }
976 }
962
977
963
978
964 #content div.box div.form div.fields div.field div.input input.small {
979 #content div.box div.form div.fields div.field div.input input.small {
965 width:30%;
980 width:30%;
966 }
981 }
967
982
968 #content div.box div.form div.fields div.field div.input input.medium {
983 #content div.box div.form div.fields div.field div.input input.medium {
969 width:55%;
984 width:55%;
970 }
985 }
971
986
972 #content div.box div.form div.fields div.field div.input input.large {
987 #content div.box div.form div.fields div.field div.input input.large {
973 width:85%;
988 width:85%;
974 }
989 }
975
990
976 #content div.box div.form div.fields div.field div.input input.date {
991 #content div.box div.form div.fields div.field div.input input.date {
977 width:177px;
992 width:177px;
978 }
993 }
979
994
980 #content div.box div.form div.fields div.field div.input input.button {
995 #content div.box div.form div.fields div.field div.input input.button {
981 background:#D4D0C8;
996 background:#D4D0C8;
982 border-top:1px solid #FFF;
997 border-top:1px solid #FFF;
983 border-left:1px solid #FFF;
998 border-left:1px solid #FFF;
984 border-right:1px solid #404040;
999 border-right:1px solid #404040;
985 border-bottom:1px solid #404040;
1000 border-bottom:1px solid #404040;
986 color:#000;
1001 color:#000;
987 margin:0;
1002 margin:0;
988 padding:4px 8px;
1003 padding:4px 8px;
989 }
1004 }
990
1005
991 #content div.box div.form div.fields div.field div.textarea {
1006 #content div.box div.form div.fields div.field div.textarea {
992 border-top:1px solid #b3b3b3;
1007 border-top:1px solid #b3b3b3;
993 border-left:1px solid #b3b3b3;
1008 border-left:1px solid #b3b3b3;
994 border-right:1px solid #eaeaea;
1009 border-right:1px solid #eaeaea;
995 border-bottom:1px solid #eaeaea;
1010 border-bottom:1px solid #eaeaea;
996 margin:0 0 0 200px;
1011 margin:0 0 0 200px;
997 padding:10px;
1012 padding:10px;
998 }
1013 }
999
1014
1000 #content div.box div.form div.fields div.field div.textarea-editor {
1015 #content div.box div.form div.fields div.field div.textarea-editor {
1001 border:1px solid #ddd;
1016 border:1px solid #ddd;
1002 padding:0;
1017 padding:0;
1003 }
1018 }
1004
1019
1005 #content div.box div.form div.fields div.field div.textarea textarea {
1020 #content div.box div.form div.fields div.field div.textarea textarea {
1006 width:100%;
1021 width:100%;
1007 height:220px;
1022 height:220px;
1008 overflow:hidden;
1023 overflow:hidden;
1009 background:#FFF;
1024 background:#FFF;
1010 color:#000;
1025 color:#000;
1011 font-family:Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
1026 font-family:Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
1012 font-size:11px;
1027 font-size:11px;
1013 outline:none;
1028 outline:none;
1014 border-width:0;
1029 border-width:0;
1015 margin:0;
1030 margin:0;
1016 padding:0;
1031 padding:0;
1017 }
1032 }
1018
1033
1019 #content div.box-left div.form div.fields div.field div.textarea textarea,#content div.box-right div.form div.fields div.field div.textarea textarea {
1034 #content div.box-left div.form div.fields div.field div.textarea textarea,#content div.box-right div.form div.fields div.field div.textarea textarea {
1020 width:100%;
1035 width:100%;
1021 height:100px;
1036 height:100px;
1022 }
1037 }
1023
1038
1024 #content div.box div.form div.fields div.field div.textarea table {
1039 #content div.box div.form div.fields div.field div.textarea table {
1025 width:100%;
1040 width:100%;
1026 border:none;
1041 border:none;
1027 margin:0;
1042 margin:0;
1028 padding:0;
1043 padding:0;
1029 }
1044 }
1030
1045
1031 #content div.box div.form div.fields div.field div.textarea table td {
1046 #content div.box div.form div.fields div.field div.textarea table td {
1032 background:#DDD;
1047 background:#DDD;
1033 border:none;
1048 border:none;
1034 padding:0;
1049 padding:0;
1035 }
1050 }
1036
1051
1037 #content div.box div.form div.fields div.field div.textarea table td table {
1052 #content div.box div.form div.fields div.field div.textarea table td table {
1038 width:auto;
1053 width:auto;
1039 border:none;
1054 border:none;
1040 margin:0;
1055 margin:0;
1041 padding:0;
1056 padding:0;
1042 }
1057 }
1043
1058
1044 #content div.box div.form div.fields div.field div.textarea table td table td {
1059 #content div.box div.form div.fields div.field div.textarea table td table td {
1045 font-family:Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
1060 font-family:Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
1046 font-size:11px;
1061 font-size:11px;
1047 padding:5px 5px 5px 0;
1062 padding:5px 5px 5px 0;
1048 }
1063 }
1049
1064
1050 #content div.box div.form div.fields div.field input[type=text]:focus,#content div.box div.form div.fields div.field input[type=password]:focus,#content div.box div.form div.fields div.field input[type=file]:focus,#content div.box div.form div.fields div.field textarea:focus,#content div.box div.form div.fields div.field select:focus {
1065 #content div.box div.form div.fields div.field input[type=text]:focus,#content div.box div.form div.fields div.field input[type=password]:focus,#content div.box div.form div.fields div.field input[type=file]:focus,#content div.box div.form div.fields div.field textarea:focus,#content div.box div.form div.fields div.field select:focus {
1051 background:#f6f6f6;
1066 background:#f6f6f6;
1052 border-color:#666;
1067 border-color:#666;
1053 }
1068 }
1054
1069
1055 div.form div.fields div.field div.button {
1070 div.form div.fields div.field div.button {
1056 margin:0;
1071 margin:0;
1057 padding:0 0 0 8px;
1072 padding:0 0 0 8px;
1058 }
1073 }
1059
1074
1060
1075
1061 #content div.box table {
1076 #content div.box table {
1062 width:100%;
1077 width:100%;
1063 border-collapse:collapse;
1078 border-collapse:collapse;
1064 margin:0;
1079 margin:0;
1065 padding:0;
1080 padding:0;
1066 border: 1px solid #eee;
1081 border: 1px solid #eee;
1067 }
1082 }
1068
1083
1069 #content div.box table th {
1084 #content div.box table th {
1070 background:#eee;
1085 background:#eee;
1071 border-bottom:1px solid #ddd;
1086 border-bottom:1px solid #ddd;
1072 padding:5px 0px 5px 5px;
1087 padding:5px 0px 5px 5px;
1073 }
1088 }
1074
1089
1075 #content div.box table th.left {
1090 #content div.box table th.left {
1076 text-align:left;
1091 text-align:left;
1077 }
1092 }
1078
1093
1079 #content div.box table th.right {
1094 #content div.box table th.right {
1080 text-align:right;
1095 text-align:right;
1081 }
1096 }
1082
1097
1083 #content div.box table th.center {
1098 #content div.box table th.center {
1084 text-align:center;
1099 text-align:center;
1085 }
1100 }
1086
1101
1087 #content div.box table th.selected {
1102 #content div.box table th.selected {
1088 vertical-align:middle;
1103 vertical-align:middle;
1089 padding:0;
1104 padding:0;
1090 }
1105 }
1091
1106
1092 #content div.box table td {
1107 #content div.box table td {
1093 background:#fff;
1108 background:#fff;
1094 border-bottom:1px solid #cdcdcd;
1109 border-bottom:1px solid #cdcdcd;
1095 vertical-align:middle;
1110 vertical-align:middle;
1096 padding:5px;
1111 padding:5px;
1097 }
1112 }
1098
1113
1099 #content div.box table tr.selected td {
1114 #content div.box table tr.selected td {
1100 background:#FFC;
1115 background:#FFC;
1101 }
1116 }
1102
1117
1103 #content div.box table td.selected {
1118 #content div.box table td.selected {
1104 width:3%;
1119 width:3%;
1105 text-align:center;
1120 text-align:center;
1106 vertical-align:middle;
1121 vertical-align:middle;
1107 padding:0;
1122 padding:0;
1108 }
1123 }
1109
1124
1110 #content div.box table td.action {
1125 #content div.box table td.action {
1111 width:45%;
1126 width:45%;
1112 text-align:left;
1127 text-align:left;
1113 }
1128 }
1114
1129
1115 #content div.box table td.date {
1130 #content div.box table td.date {
1116 width:33%;
1131 width:33%;
1117 text-align:center;
1132 text-align:center;
1118 }
1133 }
1119
1134
1120 #content div.box div.action {
1135 #content div.box div.action {
1121 float:right;
1136 float:right;
1122 background:#FFF;
1137 background:#FFF;
1123 text-align:right;
1138 text-align:right;
1124 margin:10px 0 0;
1139 margin:10px 0 0;
1125 padding:0;
1140 padding:0;
1126 }
1141 }
1127
1142
1128 #content div.box div.action select {
1143 #content div.box div.action select {
1129 font-family:Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
1144 font-family:Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
1130 font-size:11px;
1145 font-size:11px;
1131 margin:0;
1146 margin:0;
1132 }
1147 }
1133
1148
1134 #content div.box div.action .ui-selectmenu {
1149 #content div.box div.action .ui-selectmenu {
1135 margin:0;
1150 margin:0;
1136 padding:0;
1151 padding:0;
1137 }
1152 }
1138
1153
1139 #content div.box div.pagination {
1154 #content div.box div.pagination {
1140 height:1%;
1155 height:1%;
1141 clear:both;
1156 clear:both;
1142 overflow:hidden;
1157 overflow:hidden;
1143 margin:10px 0 0;
1158 margin:10px 0 0;
1144 padding:0;
1159 padding:0;
1145 }
1160 }
1146
1161
1147 #content div.box div.pagination ul.pager {
1162 #content div.box div.pagination ul.pager {
1148 float:right;
1163 float:right;
1149 text-align:right;
1164 text-align:right;
1150 margin:0;
1165 margin:0;
1151 padding:0;
1166 padding:0;
1152 }
1167 }
1153
1168
1154 #content div.box div.pagination ul.pager li {
1169 #content div.box div.pagination ul.pager li {
1155 height:1%;
1170 height:1%;
1156 float:left;
1171 float:left;
1157 list-style:none;
1172 list-style:none;
1158 background:#ebebeb url("../images/pager.png") repeat-x;
1173 background:#ebebeb url("../images/pager.png") repeat-x;
1159 border-top:1px solid #dedede;
1174 border-top:1px solid #dedede;
1160 border-left:1px solid #cfcfcf;
1175 border-left:1px solid #cfcfcf;
1161 border-right:1px solid #c4c4c4;
1176 border-right:1px solid #c4c4c4;
1162 border-bottom:1px solid #c4c4c4;
1177 border-bottom:1px solid #c4c4c4;
1163 color:#4A4A4A;
1178 color:#4A4A4A;
1164 font-weight:700;
1179 font-weight:700;
1165 margin:0 0 0 4px;
1180 margin:0 0 0 4px;
1166 padding:0;
1181 padding:0;
1167 }
1182 }
1168
1183
1169 #content div.box div.pagination ul.pager li.separator {
1184 #content div.box div.pagination ul.pager li.separator {
1170 padding:6px;
1185 padding:6px;
1171 }
1186 }
1172
1187
1173 #content div.box div.pagination ul.pager li.current {
1188 #content div.box div.pagination ul.pager li.current {
1174 background:#b4b4b4 url("../images/pager_selected.png") repeat-x;
1189 background:#b4b4b4 url("../images/pager_selected.png") repeat-x;
1175 border-top:1px solid #ccc;
1190 border-top:1px solid #ccc;
1176 border-left:1px solid #bebebe;
1191 border-left:1px solid #bebebe;
1177 border-right:1px solid #b1b1b1;
1192 border-right:1px solid #b1b1b1;
1178 border-bottom:1px solid #afafaf;
1193 border-bottom:1px solid #afafaf;
1179 color:#515151;
1194 color:#515151;
1180 padding:6px;
1195 padding:6px;
1181 }
1196 }
1182
1197
1183 #content div.box div.pagination ul.pager li a {
1198 #content div.box div.pagination ul.pager li a {
1184 height:1%;
1199 height:1%;
1185 display:block;
1200 display:block;
1186 float:left;
1201 float:left;
1187 color:#515151;
1202 color:#515151;
1188 text-decoration:none;
1203 text-decoration:none;
1189 margin:0;
1204 margin:0;
1190 padding:6px;
1205 padding:6px;
1191 }
1206 }
1192
1207
1193 #content div.box div.pagination ul.pager li a:hover,#content div.box div.pagination ul.pager li a:active {
1208 #content div.box div.pagination ul.pager li a:hover,#content div.box div.pagination ul.pager li a:active {
1194 background:#b4b4b4 url("../images/pager_selected.png") repeat-x;
1209 background:#b4b4b4 url("../images/pager_selected.png") repeat-x;
1195 border-top:1px solid #ccc;
1210 border-top:1px solid #ccc;
1196 border-left:1px solid #bebebe;
1211 border-left:1px solid #bebebe;
1197 border-right:1px solid #b1b1b1;
1212 border-right:1px solid #b1b1b1;
1198 border-bottom:1px solid #afafaf;
1213 border-bottom:1px solid #afafaf;
1199 margin:-1px;
1214 margin:-1px;
1200 }
1215 }
1201
1216
1202 #content div.box div.pagination-wh {
1217 #content div.box div.pagination-wh {
1203 height:1%;
1218 height:1%;
1204 clear:both;
1219 clear:both;
1205 overflow:hidden;
1220 overflow:hidden;
1206 text-align:right;
1221 text-align:right;
1207 margin:10px 0 0;
1222 margin:10px 0 0;
1208 padding:0;
1223 padding:0;
1209 }
1224 }
1210
1225
1211 #content div.box div.pagination-right {
1226 #content div.box div.pagination-right {
1212 float:right;
1227 float:right;
1213 }
1228 }
1214
1229
1215 #content div.box div.pagination-wh a,#content div.box div.pagination-wh span.pager_dotdot {
1230 #content div.box div.pagination-wh a,#content div.box div.pagination-wh span.pager_dotdot {
1216 height:1%;
1231 height:1%;
1217 float:left;
1232 float:left;
1218 background:#ebebeb url("../images/pager.png") repeat-x;
1233 background:#ebebeb url("../images/pager.png") repeat-x;
1219 border-top:1px solid #dedede;
1234 border-top:1px solid #dedede;
1220 border-left:1px solid #cfcfcf;
1235 border-left:1px solid #cfcfcf;
1221 border-right:1px solid #c4c4c4;
1236 border-right:1px solid #c4c4c4;
1222 border-bottom:1px solid #c4c4c4;
1237 border-bottom:1px solid #c4c4c4;
1223 color:#4A4A4A;
1238 color:#4A4A4A;
1224 font-weight:700;
1239 font-weight:700;
1225 margin:0 0 0 4px;
1240 margin:0 0 0 4px;
1226 padding:6px;
1241 padding:6px;
1227 }
1242 }
1228
1243
1229 #content div.box div.pagination-wh span.pager_curpage {
1244 #content div.box div.pagination-wh span.pager_curpage {
1230 height:1%;
1245 height:1%;
1231 float:left;
1246 float:left;
1232 background:#b4b4b4 url("../images/pager_selected.png") repeat-x;
1247 background:#b4b4b4 url("../images/pager_selected.png") repeat-x;
1233 border-top:1px solid #ccc;
1248 border-top:1px solid #ccc;
1234 border-left:1px solid #bebebe;
1249 border-left:1px solid #bebebe;
1235 border-right:1px solid #b1b1b1;
1250 border-right:1px solid #b1b1b1;
1236 border-bottom:1px solid #afafaf;
1251 border-bottom:1px solid #afafaf;
1237 color:#515151;
1252 color:#515151;
1238 font-weight:700;
1253 font-weight:700;
1239 margin:0 0 0 4px;
1254 margin:0 0 0 4px;
1240 padding:6px;
1255 padding:6px;
1241 }
1256 }
1242
1257
1243 #content div.box div.pagination-wh a:hover,#content div.box div.pagination-wh a:active {
1258 #content div.box div.pagination-wh a:hover,#content div.box div.pagination-wh a:active {
1244 background:#b4b4b4 url("../images/pager_selected.png") repeat-x;
1259 background:#b4b4b4 url("../images/pager_selected.png") repeat-x;
1245 border-top:1px solid #ccc;
1260 border-top:1px solid #ccc;
1246 border-left:1px solid #bebebe;
1261 border-left:1px solid #bebebe;
1247 border-right:1px solid #b1b1b1;
1262 border-right:1px solid #b1b1b1;
1248 border-bottom:1px solid #afafaf;
1263 border-bottom:1px solid #afafaf;
1249 text-decoration:none;
1264 text-decoration:none;
1250 }
1265 }
1251
1266
1252 #content div.box div.traffic div.legend {
1267 #content div.box div.traffic div.legend {
1253 clear:both;
1268 clear:both;
1254 overflow:hidden;
1269 overflow:hidden;
1255 border-bottom:1px solid #ddd;
1270 border-bottom:1px solid #ddd;
1256 margin:0 0 10px;
1271 margin:0 0 10px;
1257 padding:0 0 10px;
1272 padding:0 0 10px;
1258 }
1273 }
1259
1274
1260 #content div.box div.traffic div.legend h6 {
1275 #content div.box div.traffic div.legend h6 {
1261 float:left;
1276 float:left;
1262 border:none;
1277 border:none;
1263 margin:0;
1278 margin:0;
1264 padding:0;
1279 padding:0;
1265 }
1280 }
1266
1281
1267 #content div.box div.traffic div.legend li {
1282 #content div.box div.traffic div.legend li {
1268 list-style:none;
1283 list-style:none;
1269 float:left;
1284 float:left;
1270 font-size:11px;
1285 font-size:11px;
1271 margin:0;
1286 margin:0;
1272 padding:0 8px 0 4px;
1287 padding:0 8px 0 4px;
1273 }
1288 }
1274
1289
1275 #content div.box div.traffic div.legend li.visits {
1290 #content div.box div.traffic div.legend li.visits {
1276 border-left:12px solid #edc240;
1291 border-left:12px solid #edc240;
1277 }
1292 }
1278
1293
1279 #content div.box div.traffic div.legend li.pageviews {
1294 #content div.box div.traffic div.legend li.pageviews {
1280 border-left:12px solid #afd8f8;
1295 border-left:12px solid #afd8f8;
1281 }
1296 }
1282
1297
1283 #content div.box div.traffic table {
1298 #content div.box div.traffic table {
1284 width:auto;
1299 width:auto;
1285 }
1300 }
1286
1301
1287 #content div.box div.traffic table td {
1302 #content div.box div.traffic table td {
1288 background:transparent;
1303 background:transparent;
1289 border:none;
1304 border:none;
1290 padding:2px 3px 3px;
1305 padding:2px 3px 3px;
1291 }
1306 }
1292
1307
1293 #content div.box div.traffic table td.legendLabel {
1308 #content div.box div.traffic table td.legendLabel {
1294 padding:0 3px 2px;
1309 padding:0 3px 2px;
1295 }
1310 }
1296
1311
1297 #summary{
1312 #summary{
1298
1313
1299 }
1314 }
1300
1315
1301 #summary .desc{
1316 #summary .desc{
1302 white-space: pre;
1317 white-space: pre;
1303 width: 100%;
1318 width: 100%;
1304 }
1319 }
1305
1320
1306 #summary .repo_name{
1321 #summary .repo_name{
1307 font-size: 1.6em;
1322 font-size: 1.6em;
1308 font-weight: bold;
1323 font-weight: bold;
1309 vertical-align: baseline;
1324 vertical-align: baseline;
1310 clear:right
1325 clear:right
1311 }
1326 }
1312
1327
1313
1328
1314 #footer {
1329 #footer {
1315 clear:both;
1330 clear:both;
1316 overflow:hidden;
1331 overflow:hidden;
1317 text-align:right;
1332 text-align:right;
1318 margin:0;
1333 margin:0;
1319 padding:0 10px 4px;
1334 padding:0 10px 4px;
1320 margin:-10px 0 0;
1335 margin:-10px 0 0;
1321 }
1336 }
1322
1337
1323 #footer div#footer-inner {
1338 #footer div#footer-inner {
1324 background:url("../images/header_inner.png") repeat-x scroll 0 0 #003367;
1339 background:url("../images/header_inner.png") repeat-x scroll 0 0 #003367;
1325 box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
1340 box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
1326 -webkit-border-radius: 4px 4px 4px 4px;
1341 -webkit-border-radius: 4px 4px 4px 4px;
1327 -khtml-border-radius: 4px 4px 4px 4px;
1342 -khtml-border-radius: 4px 4px 4px 4px;
1328 -moz-border-radius: 4px 4px 4px 4px;
1343 -moz-border-radius: 4px 4px 4px 4px;
1329 border-radius: 4px 4px 4px 4px;
1344 border-radius: 4px 4px 4px 4px;
1330 }
1345 }
1331
1346
1332 #footer div#footer-inner p {
1347 #footer div#footer-inner p {
1333 padding:15px 25px 15px 0;
1348 padding:15px 25px 15px 0;
1334 color:#FFF;
1349 color:#FFF;
1335 font-weight:700;
1350 font-weight:700;
1336 }
1351 }
1337 #footer div#footer-inner .footer-link {
1352 #footer div#footer-inner .footer-link {
1338 float:left;
1353 float:left;
1339 padding-left:10px;
1354 padding-left:10px;
1340 }
1355 }
1341 #footer div#footer-inner .footer-link a,#footer div#footer-inner .footer-link-right a {
1356 #footer div#footer-inner .footer-link a,#footer div#footer-inner .footer-link-right a {
1342 color:#FFF;
1357 color:#FFF;
1343 }
1358 }
1344
1359
1345 #login div.title {
1360 #login div.title {
1346 width:420px;
1361 width:420px;
1347 clear:both;
1362 clear:both;
1348 overflow:hidden;
1363 overflow:hidden;
1349 position:relative;
1364 position:relative;
1350 background:#003367 url("../images/header_inner.png") repeat-x;
1365 background:#003367 url("../images/header_inner.png") repeat-x;
1351 margin:0 auto;
1366 margin:0 auto;
1352 padding:0;
1367 padding:0;
1353 }
1368 }
1354
1369
1355 #login div.inner {
1370 #login div.inner {
1356 width:380px;
1371 width:380px;
1357 background:#FFF url("../images/login.png") no-repeat top left;
1372 background:#FFF url("../images/login.png") no-repeat top left;
1358 border-top:none;
1373 border-top:none;
1359 border-bottom:none;
1374 border-bottom:none;
1360 margin:0 auto;
1375 margin:0 auto;
1361 padding:20px;
1376 padding:20px;
1362 }
1377 }
1363
1378
1364 #login div.form div.fields div.field div.label {
1379 #login div.form div.fields div.field div.label {
1365 width:173px;
1380 width:173px;
1366 float:left;
1381 float:left;
1367 text-align:right;
1382 text-align:right;
1368 margin:2px 10px 0 0;
1383 margin:2px 10px 0 0;
1369 padding:5px 0 0 5px;
1384 padding:5px 0 0 5px;
1370 }
1385 }
1371
1386
1372 #login div.form div.fields div.field div.input input {
1387 #login div.form div.fields div.field div.input input {
1373 width:176px;
1388 width:176px;
1374 background:#FFF;
1389 background:#FFF;
1375 border-top:1px solid #b3b3b3;
1390 border-top:1px solid #b3b3b3;
1376 border-left:1px solid #b3b3b3;
1391 border-left:1px solid #b3b3b3;
1377 border-right:1px solid #eaeaea;
1392 border-right:1px solid #eaeaea;
1378 border-bottom:1px solid #eaeaea;
1393 border-bottom:1px solid #eaeaea;
1379 color:#000;
1394 color:#000;
1380 font-family:Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
1395 font-family:Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
1381 font-size:11px;
1396 font-size:11px;
1382 margin:0;
1397 margin:0;
1383 padding:7px 7px 6px;
1398 padding:7px 7px 6px;
1384 }
1399 }
1385
1400
1386 #login div.form div.fields div.buttons {
1401 #login div.form div.fields div.buttons {
1387 clear:both;
1402 clear:both;
1388 overflow:hidden;
1403 overflow:hidden;
1389 border-top:1px solid #DDD;
1404 border-top:1px solid #DDD;
1390 text-align:right;
1405 text-align:right;
1391 margin:0;
1406 margin:0;
1392 padding:10px 0 0;
1407 padding:10px 0 0;
1393 }
1408 }
1394
1409
1395 #login div.form div.links {
1410 #login div.form div.links {
1396 clear:both;
1411 clear:both;
1397 overflow:hidden;
1412 overflow:hidden;
1398 margin:10px 0 0;
1413 margin:10px 0 0;
1399 padding:0 0 2px;
1414 padding:0 0 2px;
1400 }
1415 }
1401
1416
1402 #quick_login{
1417 #quick_login{
1403 top: 31px;
1418 top: 31px;
1404 background-color: rgb(0, 51, 103);
1419 background-color: rgb(0, 51, 103);
1405 z-index: 999;
1420 z-index: 999;
1406 height: 150px;
1421 height: 150px;
1407 position: absolute;
1422 position: absolute;
1408 margin-left: -16px;
1423 margin-left: -16px;
1409 width: 281px;
1424 width: 281px;
1410 -webkit-border-radius: 0px 0px 4px 4px;
1425 -webkit-border-radius: 0px 0px 4px 4px;
1411 -khtml-border-radius: 0px 0px 4px 4px;
1426 -khtml-border-radius: 0px 0px 4px 4px;
1412 -moz-border-radius: 0px 0px 4px 4px;
1427 -moz-border-radius: 0px 0px 4px 4px;
1413 border-radius: 0px 0px 4px 4px;
1428 border-radius: 0px 0px 4px 4px;
1414
1429
1415 box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
1430 box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
1416 }
1431 }
1417
1432
1418 #quick_login .password_forgoten{
1433 #quick_login .password_forgoten{
1419 padding-right:10px;
1434 padding-right:10px;
1420 padding-top:0px;
1435 padding-top:0px;
1421 float:left;
1436 float:left;
1422 }
1437 }
1423 #quick_login .password_forgoten a{
1438 #quick_login .password_forgoten a{
1424 font-size: 10px
1439 font-size: 10px
1425 }
1440 }
1426
1441
1427 #quick_login .register{
1442 #quick_login .register{
1428 padding-right:10px;
1443 padding-right:10px;
1429 padding-top:5px;
1444 padding-top:5px;
1430 float:left;
1445 float:left;
1431 }
1446 }
1432
1447
1433 #quick_login .register a{
1448 #quick_login .register a{
1434 font-size: 10px
1449 font-size: 10px
1435 }
1450 }
1436 #quick_login div.form div.fields{
1451 #quick_login div.form div.fields{
1437 padding-top: 2px;
1452 padding-top: 2px;
1438 padding-left:10px;
1453 padding-left:10px;
1439 }
1454 }
1440
1455
1441 #quick_login div.form div.fields div.field{
1456 #quick_login div.form div.fields div.field{
1442 padding: 5px;
1457 padding: 5px;
1443 }
1458 }
1444
1459
1445 #quick_login div.form div.fields div.field div.label label{
1460 #quick_login div.form div.fields div.field div.label label{
1446 color:#fff;
1461 color:#fff;
1447 padding-bottom: 3px;
1462 padding-bottom: 3px;
1448 }
1463 }
1449
1464
1450 #quick_login div.form div.fields div.field div.input input {
1465 #quick_login div.form div.fields div.field div.input input {
1451 width:236px;
1466 width:236px;
1452 background:#FFF;
1467 background:#FFF;
1453 border-top:1px solid #b3b3b3;
1468 border-top:1px solid #b3b3b3;
1454 border-left:1px solid #b3b3b3;
1469 border-left:1px solid #b3b3b3;
1455 border-right:1px solid #eaeaea;
1470 border-right:1px solid #eaeaea;
1456 border-bottom:1px solid #eaeaea;
1471 border-bottom:1px solid #eaeaea;
1457 color:#000;
1472 color:#000;
1458 font-family:Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
1473 font-family:Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
1459 font-size:11px;
1474 font-size:11px;
1460 margin:0;
1475 margin:0;
1461 padding:5px 7px 4px;
1476 padding:5px 7px 4px;
1462 }
1477 }
1463
1478
1464 #quick_login div.form div.fields div.buttons {
1479 #quick_login div.form div.fields div.buttons {
1465 clear:both;
1480 clear:both;
1466 overflow:hidden;
1481 overflow:hidden;
1467 text-align:right;
1482 text-align:right;
1468 margin:0;
1483 margin:0;
1469 padding:10px 14px 0px 5px;
1484 padding:10px 14px 0px 5px;
1470 }
1485 }
1471
1486
1472 #quick_login div.form div.links {
1487 #quick_login div.form div.links {
1473 clear:both;
1488 clear:both;
1474 overflow:hidden;
1489 overflow:hidden;
1475 margin:10px 0 0;
1490 margin:10px 0 0;
1476 padding:0 0 2px;
1491 padding:0 0 2px;
1477 }
1492 }
1478
1493
1479 #register div.title {
1494 #register div.title {
1480 clear:both;
1495 clear:both;
1481 overflow:hidden;
1496 overflow:hidden;
1482 position:relative;
1497 position:relative;
1483 background:#003367 url("../images/header_inner.png") repeat-x;
1498 background:#003367 url("../images/header_inner.png") repeat-x;
1484 margin:0 auto;
1499 margin:0 auto;
1485 padding:0;
1500 padding:0;
1486 }
1501 }
1487
1502
1488 #register div.inner {
1503 #register div.inner {
1489 background:#FFF;
1504 background:#FFF;
1490 border-top:none;
1505 border-top:none;
1491 border-bottom:none;
1506 border-bottom:none;
1492 margin:0 auto;
1507 margin:0 auto;
1493 padding:20px;
1508 padding:20px;
1494 }
1509 }
1495
1510
1496 #register div.form div.fields div.field div.label {
1511 #register div.form div.fields div.field div.label {
1497 width:135px;
1512 width:135px;
1498 float:left;
1513 float:left;
1499 text-align:right;
1514 text-align:right;
1500 margin:2px 10px 0 0;
1515 margin:2px 10px 0 0;
1501 padding:5px 0 0 5px;
1516 padding:5px 0 0 5px;
1502 }
1517 }
1503
1518
1504 #register div.form div.fields div.field div.input input {
1519 #register div.form div.fields div.field div.input input {
1505 width:300px;
1520 width:300px;
1506 background:#FFF;
1521 background:#FFF;
1507 border-top:1px solid #b3b3b3;
1522 border-top:1px solid #b3b3b3;
1508 border-left:1px solid #b3b3b3;
1523 border-left:1px solid #b3b3b3;
1509 border-right:1px solid #eaeaea;
1524 border-right:1px solid #eaeaea;
1510 border-bottom:1px solid #eaeaea;
1525 border-bottom:1px solid #eaeaea;
1511 color:#000;
1526 color:#000;
1512 font-family:Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
1527 font-family:Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
1513 font-size:11px;
1528 font-size:11px;
1514 margin:0;
1529 margin:0;
1515 padding:7px 7px 6px;
1530 padding:7px 7px 6px;
1516 }
1531 }
1517
1532
1518 #register div.form div.fields div.buttons {
1533 #register div.form div.fields div.buttons {
1519 clear:both;
1534 clear:both;
1520 overflow:hidden;
1535 overflow:hidden;
1521 border-top:1px solid #DDD;
1536 border-top:1px solid #DDD;
1522 text-align:left;
1537 text-align:left;
1523 margin:0;
1538 margin:0;
1524 padding:10px 0 0 150px;
1539 padding:10px 0 0 150px;
1525 }
1540 }
1526
1541
1527
1542
1528 #register div.form div.activation_msg {
1543 #register div.form div.activation_msg {
1529 padding-top:4px;
1544 padding-top:4px;
1530 padding-bottom:4px;
1545 padding-bottom:4px;
1531 }
1546 }
1532
1547
1533 #journal .journal_day{
1548 #journal .journal_day{
1534 font-size:20px;
1549 font-size:20px;
1535 padding:10px 0px;
1550 padding:10px 0px;
1536 border-bottom:2px solid #DDD;
1551 border-bottom:2px solid #DDD;
1537 margin-left:10px;
1552 margin-left:10px;
1538 margin-right:10px;
1553 margin-right:10px;
1539 }
1554 }
1540
1555
1541 #journal .journal_container{
1556 #journal .journal_container{
1542 padding:5px;
1557 padding:5px;
1543 clear:both;
1558 clear:both;
1544 margin:0px 5px 0px 10px;
1559 margin:0px 5px 0px 10px;
1545 }
1560 }
1546
1561
1547 #journal .journal_action_container{
1562 #journal .journal_action_container{
1548 padding-left:38px;
1563 padding-left:38px;
1549 }
1564 }
1550
1565
1551 #journal .journal_user{
1566 #journal .journal_user{
1552 color: #747474;
1567 color: #747474;
1553 font-size: 14px;
1568 font-size: 14px;
1554 font-weight: bold;
1569 font-weight: bold;
1555 height: 30px;
1570 height: 30px;
1556 }
1571 }
1557 #journal .journal_icon{
1572 #journal .journal_icon{
1558 clear: both;
1573 clear: both;
1559 float: left;
1574 float: left;
1560 padding-right: 4px;
1575 padding-right: 4px;
1561 padding-top: 3px;
1576 padding-top: 3px;
1562 }
1577 }
1563 #journal .journal_action{
1578 #journal .journal_action{
1564 padding-top:4px;
1579 padding-top:4px;
1565 min-height:2px;
1580 min-height:2px;
1566 float:left
1581 float:left
1567 }
1582 }
1568 #journal .journal_action_params{
1583 #journal .journal_action_params{
1569 clear: left;
1584 clear: left;
1570 padding-left: 22px;
1585 padding-left: 22px;
1571 }
1586 }
1572 #journal .journal_repo{
1587 #journal .journal_repo{
1573 float: left;
1588 float: left;
1574 margin-left: 6px;
1589 margin-left: 6px;
1575 padding-top: 3px;
1590 padding-top: 3px;
1576 }
1591 }
1577 #journal .date{
1592 #journal .date{
1578 clear: both;
1593 clear: both;
1579 color: #777777;
1594 color: #777777;
1580 font-size: 11px;
1595 font-size: 11px;
1581 padding-left: 22px;
1596 padding-left: 22px;
1582 }
1597 }
1583 #journal .journal_repo .journal_repo_name{
1598 #journal .journal_repo .journal_repo_name{
1584 font-weight: bold;
1599 font-weight: bold;
1585 font-size: 1.1em;
1600 font-size: 1.1em;
1586 }
1601 }
1587 #journal .compare_view{
1602 #journal .compare_view{
1588 padding: 5px 0px 5px 0px;
1603 padding: 5px 0px 5px 0px;
1589 width: 95px;
1604 width: 95px;
1590 }
1605 }
1591 .journal_highlight{
1606 .journal_highlight{
1592 font-weight: bold;
1607 font-weight: bold;
1593 padding: 0 2px;
1608 padding: 0 2px;
1594 vertical-align: bottom;
1609 vertical-align: bottom;
1595 }
1610 }
1596 .trending_language_tbl,.trending_language_tbl td {
1611 .trending_language_tbl,.trending_language_tbl td {
1597 border:0 !important;
1612 border:0 !important;
1598 margin:0 !important;
1613 margin:0 !important;
1599 padding:0 !important;
1614 padding:0 !important;
1600 }
1615 }
1601
1616
1602 .trending_language {
1617 .trending_language {
1603 background-color:#003367;
1618 background-color:#003367;
1604 color:#FFF;
1619 color:#FFF;
1605 display:block;
1620 display:block;
1606 min-width:20px;
1621 min-width:20px;
1607 text-decoration:none;
1622 text-decoration:none;
1608 height:12px;
1623 height:12px;
1609 margin-bottom:4px;
1624 margin-bottom:4px;
1610 margin-left:5px;
1625 margin-left:5px;
1611 white-space:pre;
1626 white-space:pre;
1612 padding:3px;
1627 padding:3px;
1613 }
1628 }
1614
1629
1615 h3.files_location {
1630 h3.files_location {
1616 font-size:1.8em;
1631 font-size:1.8em;
1617 font-weight:700;
1632 font-weight:700;
1618 border-bottom:none !important;
1633 border-bottom:none !important;
1619 margin:10px 0 !important;
1634 margin:10px 0 !important;
1620 }
1635 }
1621
1636
1622 #files_data dl dt {
1637 #files_data dl dt {
1623 float:left;
1638 float:left;
1624 width:115px;
1639 width:115px;
1625 margin:0 !important;
1640 margin:0 !important;
1626 padding:5px;
1641 padding:5px;
1627 }
1642 }
1628
1643
1629 #files_data dl dd {
1644 #files_data dl dd {
1630 margin:0 !important;
1645 margin:0 !important;
1631 padding:5px !important;
1646 padding:5px !important;
1632 }
1647 }
1633
1648
1634 #changeset_content {
1649 #changeset_content {
1635 border:1px solid #CCC;
1650 border:1px solid #CCC;
1636 padding:5px;
1651 padding:5px;
1637 }
1652 }
1638 #changeset_compare_view_content{
1653 #changeset_compare_view_content{
1639 border:1px solid #CCC;
1654 border:1px solid #CCC;
1640 padding:5px;
1655 padding:5px;
1641 }
1656 }
1642
1657
1643 #changeset_content .container {
1658 #changeset_content .container {
1644 min-height:120px;
1659 min-height:120px;
1645 font-size:1.2em;
1660 font-size:1.2em;
1646 overflow:hidden;
1661 overflow:hidden;
1647 }
1662 }
1648
1663
1649 #changeset_compare_view_content .compare_view_commits{
1664 #changeset_compare_view_content .compare_view_commits{
1650 width: auto !important;
1665 width: auto !important;
1651 }
1666 }
1652
1667
1653 #changeset_compare_view_content .compare_view_commits td{
1668 #changeset_compare_view_content .compare_view_commits td{
1654 padding:0px 0px 0px 12px !important;
1669 padding:0px 0px 0px 12px !important;
1655 }
1670 }
1656
1671
1657 #changeset_content .container .right {
1672 #changeset_content .container .right {
1658 float:right;
1673 float:right;
1659 width:25%;
1674 width:25%;
1660 text-align:right;
1675 text-align:right;
1661 }
1676 }
1662
1677
1663 #changeset_content .container .left .message {
1678 #changeset_content .container .left .message {
1664 font-style:italic;
1679 font-style:italic;
1665 color:#556CB5;
1680 color:#556CB5;
1666 white-space:pre-wrap;
1681 white-space:pre-wrap;
1667 }
1682 }
1668
1683
1669 .cs_files .cur_cs{
1684 .cs_files .cur_cs{
1670 margin:10px 2px;
1685 margin:10px 2px;
1671 font-weight: bold;
1686 font-weight: bold;
1672 }
1687 }
1673
1688
1674 .cs_files .node{
1689 .cs_files .node{
1675 float: left;
1690 float: left;
1676 }
1691 }
1677 .cs_files .changes{
1692 .cs_files .changes{
1678 float: right;
1693 float: right;
1679 }
1694 }
1680 .cs_files .changes .added{
1695 .cs_files .changes .added{
1681 background-color: #BBFFBB;
1696 background-color: #BBFFBB;
1682 float: left;
1697 float: left;
1683 text-align: center;
1698 text-align: center;
1684 font-size: 90%;
1699 font-size: 90%;
1685 }
1700 }
1686 .cs_files .changes .deleted{
1701 .cs_files .changes .deleted{
1687 background-color: #FF8888;
1702 background-color: #FF8888;
1688 float: left;
1703 float: left;
1689 text-align: center;
1704 text-align: center;
1690 font-size: 90%;
1705 font-size: 90%;
1691 }
1706 }
1692 .cs_files .cs_added {
1707 .cs_files .cs_added {
1693 background:url("../images/icons/page_white_add.png") no-repeat scroll 3px;
1708 background:url("../images/icons/page_white_add.png") no-repeat scroll 3px;
1694 height:16px;
1709 height:16px;
1695 padding-left:20px;
1710 padding-left:20px;
1696 margin-top:7px;
1711 margin-top:7px;
1697 text-align:left;
1712 text-align:left;
1698 }
1713 }
1699
1714
1700 .cs_files .cs_changed {
1715 .cs_files .cs_changed {
1701 background:url("../images/icons/page_white_edit.png") no-repeat scroll 3px;
1716 background:url("../images/icons/page_white_edit.png") no-repeat scroll 3px;
1702 height:16px;
1717 height:16px;
1703 padding-left:20px;
1718 padding-left:20px;
1704 margin-top:7px;
1719 margin-top:7px;
1705 text-align:left;
1720 text-align:left;
1706 }
1721 }
1707
1722
1708 .cs_files .cs_removed {
1723 .cs_files .cs_removed {
1709 background:url("../images/icons/page_white_delete.png") no-repeat scroll 3px;
1724 background:url("../images/icons/page_white_delete.png") no-repeat scroll 3px;
1710 height:16px;
1725 height:16px;
1711 padding-left:20px;
1726 padding-left:20px;
1712 margin-top:7px;
1727 margin-top:7px;
1713 text-align:left;
1728 text-align:left;
1714 }
1729 }
1715
1730
1716 #graph {
1731 #graph {
1717 overflow:hidden;
1732 overflow:hidden;
1718 }
1733 }
1719
1734
1720 #graph_nodes {
1735 #graph_nodes {
1721 float: left;
1736 float: left;
1722 margin-right: -6px;
1737 margin-right: -6px;
1723 margin-top: -4px;
1738 margin-top: -4px;
1724 }
1739 }
1725
1740
1726 #graph_content {
1741 #graph_content {
1727 width:800px;
1742 width:800px;
1728 float:left;
1743 float:left;
1729
1744
1730 }
1745 }
1731
1746
1732 #graph_content .container_header {
1747 #graph_content .container_header {
1733 border:1px solid #CCC;
1748 border:1px solid #CCC;
1734 padding:10px;
1749 padding:10px;
1735 }
1750 }
1736 #graph_content #rev_range_container{
1751 #graph_content #rev_range_container{
1737 padding:10px 0px;
1752 padding:10px 0px;
1738 }
1753 }
1739 #graph_content .container {
1754 #graph_content .container {
1740 border-bottom:1px solid #CCC;
1755 border-bottom:1px solid #CCC;
1741 border-left:1px solid #CCC;
1756 border-left:1px solid #CCC;
1742 border-right:1px solid #CCC;
1757 border-right:1px solid #CCC;
1743 min-height:70px;
1758 min-height:70px;
1744 overflow:hidden;
1759 overflow:hidden;
1745 font-size:1.2em;
1760 font-size:1.2em;
1746 }
1761 }
1747
1762
1748 #graph_content .container .right {
1763 #graph_content .container .right {
1749 float:right;
1764 float:right;
1750 width:28%;
1765 width:28%;
1751 text-align:right;
1766 text-align:right;
1752 padding-bottom:5px;
1767 padding-bottom:5px;
1753 }
1768 }
1754
1769
1755 #graph_content .container .left .date {
1770 #graph_content .container .left .date {
1756 font-weight:700;
1771 font-weight:700;
1757 padding-bottom:5px;
1772 padding-bottom:5px;
1758 }
1773 }
1759 #graph_content .container .left .date span{
1774 #graph_content .container .left .date span{
1760 vertical-align: text-top;
1775 vertical-align: text-top;
1761 }
1776 }
1762
1777
1763 #graph_content .container .left .author{
1778 #graph_content .container .left .author{
1764 height: 22px;
1779 height: 22px;
1765 }
1780 }
1766 #graph_content .container .left .author .user{
1781 #graph_content .container .left .author .user{
1767 color: #444444;
1782 color: #444444;
1768 float: left;
1783 float: left;
1769 font-size: 12px;
1784 font-size: 12px;
1770 margin-left: -4px;
1785 margin-left: -4px;
1771 margin-top: 4px;
1786 margin-top: 4px;
1772 }
1787 }
1773
1788
1774 #graph_content .container .left .message {
1789 #graph_content .container .left .message {
1775 font-size:100%;
1790 font-size:100%;
1776 padding-top:3px;
1791 padding-top:3px;
1777 white-space:pre-wrap;
1792 white-space:pre-wrap;
1778 }
1793 }
1779
1794
1780 .right div {
1795 .right div {
1781 clear:both;
1796 clear:both;
1782 }
1797 }
1783
1798
1784 .right .changes .changed_total{
1799 .right .changes .changed_total{
1785 border:1px solid #DDD;
1800 border:1px solid #DDD;
1786 display:block;
1801 display:block;
1787 float:right;
1802 float:right;
1788 text-align:center;
1803 text-align:center;
1789 min-width:45px;
1804 min-width:45px;
1790 cursor: pointer;
1805 cursor: pointer;
1791 background:#FD8;
1806 background:#FD8;
1792 font-weight: bold;
1807 font-weight: bold;
1793 }
1808 }
1794 .right .changes .added,.changed,.removed {
1809 .right .changes .added,.changed,.removed {
1795 border:1px solid #DDD;
1810 border:1px solid #DDD;
1796 display:block;
1811 display:block;
1797 float:right;
1812 float:right;
1798 text-align:center;
1813 text-align:center;
1799 min-width:15px;
1814 min-width:15px;
1800 cursor: help;
1815 cursor: help;
1801 }
1816 }
1802 .right .changes .large {
1817 .right .changes .large {
1803 border:1px solid #DDD;
1818 border:1px solid #DDD;
1804 display:block;
1819 display:block;
1805 float:right;
1820 float:right;
1806 text-align:center;
1821 text-align:center;
1807 min-width:45px;
1822 min-width:45px;
1808 cursor: help;
1823 cursor: help;
1809 background: #54A9F7;
1824 background: #54A9F7;
1810 }
1825 }
1811
1826
1812 .right .changes .added {
1827 .right .changes .added {
1813 background:#BFB;
1828 background:#BFB;
1814 }
1829 }
1815
1830
1816 .right .changes .changed {
1831 .right .changes .changed {
1817 background:#FD8;
1832 background:#FD8;
1818 }
1833 }
1819
1834
1820 .right .changes .removed {
1835 .right .changes .removed {
1821 background:#F88;
1836 background:#F88;
1822 }
1837 }
1823
1838
1824 .right .merge {
1839 .right .merge {
1825 vertical-align:top;
1840 vertical-align:top;
1826 font-size:0.75em;
1841 font-size:0.75em;
1827 font-weight:700;
1842 font-weight:700;
1828 }
1843 }
1829
1844
1830 .right .parent {
1845 .right .parent {
1831 font-size:90%;
1846 font-size:90%;
1832 font-family:monospace;
1847 font-family:monospace;
1833 }
1848 }
1834
1849
1835 .right .logtags .branchtag {
1850 .right .logtags .branchtag {
1836 background:#FFF url("../images/icons/arrow_branch.png") no-repeat right 6px;
1851 background:#FFF url("../images/icons/arrow_branch.png") no-repeat right 6px;
1837 display:block;
1852 display:block;
1838 font-size:0.8em;
1853 font-size:0.8em;
1839 padding:11px 16px 0 0;
1854 padding:11px 16px 0 0;
1840 }
1855 }
1841
1856
1842 .right .logtags .tagtag {
1857 .right .logtags .tagtag {
1843 background:#FFF url("../images/icons/tag_blue.png") no-repeat right 6px;
1858 background:#FFF url("../images/icons/tag_blue.png") no-repeat right 6px;
1844 display:block;
1859 display:block;
1845 font-size:0.8em;
1860 font-size:0.8em;
1846 padding:11px 16px 0 0;
1861 padding:11px 16px 0 0;
1847 }
1862 }
1848
1863
1849 div.browserblock {
1864 div.browserblock {
1850 overflow:hidden;
1865 overflow:hidden;
1851 border:1px solid #ccc;
1866 border:1px solid #ccc;
1852 background:#f8f8f8;
1867 background:#f8f8f8;
1853 font-size:100%;
1868 font-size:100%;
1854 line-height:125%;
1869 line-height:125%;
1855 padding:0;
1870 padding:0;
1856 }
1871 }
1857
1872
1858 div.browserblock .browser-header {
1873 div.browserblock .browser-header {
1859 background:#FFF;
1874 background:#FFF;
1860 padding:10px 0px 15px 0px;
1875 padding:10px 0px 15px 0px;
1861 width: 100%;
1876 width: 100%;
1862 }
1877 }
1863 div.browserblock .browser-nav {
1878 div.browserblock .browser-nav {
1864 float:left
1879 float:left
1865 }
1880 }
1866
1881
1867 div.browserblock .browser-branch {
1882 div.browserblock .browser-branch {
1868 float:left;
1883 float:left;
1869 }
1884 }
1870
1885
1871 div.browserblock .browser-branch label {
1886 div.browserblock .browser-branch label {
1872 color:#4A4A4A;
1887 color:#4A4A4A;
1873 vertical-align:text-top;
1888 vertical-align:text-top;
1874 }
1889 }
1875
1890
1876 div.browserblock .browser-header span {
1891 div.browserblock .browser-header span {
1877 margin-left:5px;
1892 margin-left:5px;
1878 font-weight:700;
1893 font-weight:700;
1879 }
1894 }
1880
1895
1881 div.browserblock .browser-search{
1896 div.browserblock .browser-search{
1882 clear:both;
1897 clear:both;
1883 padding:8px 8px 0px 5px;
1898 padding:8px 8px 0px 5px;
1884 height: 20px;
1899 height: 20px;
1885 }
1900 }
1886 div.browserblock #node_filter_box {
1901 div.browserblock #node_filter_box {
1887 }
1902 }
1888
1903
1889 div.browserblock .search_activate{
1904 div.browserblock .search_activate{
1890 float: left
1905 float: left
1891 }
1906 }
1892
1907
1893 div.browserblock .add_node{
1908 div.browserblock .add_node{
1894 float: left;
1909 float: left;
1895 padding-left: 5px;
1910 padding-left: 5px;
1896 }
1911 }
1897
1912
1898 div.browserblock .search_activate #filter_activate,div.browserblock .add_node a{
1899 vertical-align: sub;
1900 border: 1px solid;
1901 padding:2px;
1902 -webkit-border-radius: 4px 4px 4px 4px;
1903 -khtml-border-radius: 4px 4px 4px 4px;
1904 -moz-border-radius: 4px 4px 4px 4px;
1905 border-radius: 4px 4px 4px 4px;
1906 background: url("../images/button.png") repeat-x scroll 0 0 #E5E3E3;
1907 border-color: #DDDDDD #DDDDDD #C6C6C6 #C6C6C6;
1908 color: #515151;
1909 }
1910
1911 div.browserblock .search_activate a:hover,div.browserblock .add_node a:hover{
1913 div.browserblock .search_activate a:hover,div.browserblock .add_node a:hover{
1912 text-decoration: none !important;
1914 text-decoration: none !important;
1913 }
1915 }
1914
1916
1915 div.browserblock .browser-body {
1917 div.browserblock .browser-body {
1916 background:#EEE;
1918 background:#EEE;
1917 border-top:1px solid #CCC;
1919 border-top:1px solid #CCC;
1918 }
1920 }
1919
1921
1920 table.code-browser {
1922 table.code-browser {
1921 border-collapse:collapse;
1923 border-collapse:collapse;
1922 width:100%;
1924 width:100%;
1923 }
1925 }
1924
1926
1925 table.code-browser tr {
1927 table.code-browser tr {
1926 margin:3px;
1928 margin:3px;
1927 }
1929 }
1928
1930
1929 table.code-browser thead th {
1931 table.code-browser thead th {
1930 background-color:#EEE;
1932 background-color:#EEE;
1931 height:20px;
1933 height:20px;
1932 font-size:1.1em;
1934 font-size:1.1em;
1933 font-weight:700;
1935 font-weight:700;
1934 text-align:left;
1936 text-align:left;
1935 padding-left:10px;
1937 padding-left:10px;
1936 }
1938 }
1937
1939
1938 table.code-browser tbody td {
1940 table.code-browser tbody td {
1939 padding-left:10px;
1941 padding-left:10px;
1940 height:20px;
1942 height:20px;
1941 }
1943 }
1942
1944
1943 table.code-browser .browser-file {
1945 table.code-browser .browser-file {
1944 background:url("../images/icons/document_16.png") no-repeat scroll 3px;
1946 background:url("../images/icons/document_16.png") no-repeat scroll 3px;
1945 height:16px;
1947 height:16px;
1946 padding-left:20px;
1948 padding-left:20px;
1947 text-align:left;
1949 text-align:left;
1948 }
1950 }
1949 .diffblock .changeset_file{
1951 .diffblock .changeset_file{
1950 background:url("../images/icons/file.png") no-repeat scroll 3px;
1952 background:url("../images/icons/file.png") no-repeat scroll 3px;
1951 height:16px;
1953 height:16px;
1952 padding-left:22px;
1954 padding-left:22px;
1953 text-align:left;
1955 text-align:left;
1954 font-size: 14px;
1956 font-size: 14px;
1955 }
1957 }
1956
1958
1957 .diffblock .changeset_header{
1959 .diffblock .changeset_header{
1958 margin-left: 6px !important;
1960 margin-left: 6px !important;
1959 }
1961 }
1960
1962
1961 table.code-browser .browser-dir {
1963 table.code-browser .browser-dir {
1962 background:url("../images/icons/folder_16.png") no-repeat scroll 3px;
1964 background:url("../images/icons/folder_16.png") no-repeat scroll 3px;
1963 height:16px;
1965 height:16px;
1964 padding-left:20px;
1966 padding-left:20px;
1965 text-align:left;
1967 text-align:left;
1966 }
1968 }
1967
1969
1968 .box .search {
1970 .box .search {
1969 clear:both;
1971 clear:both;
1970 overflow:hidden;
1972 overflow:hidden;
1971 margin:0;
1973 margin:0;
1972 padding:0 20px 10px;
1974 padding:0 20px 10px;
1973 }
1975 }
1974
1976
1975 .box .search div.search_path {
1977 .box .search div.search_path {
1976 background:none repeat scroll 0 0 #EEE;
1978 background:none repeat scroll 0 0 #EEE;
1977 border:1px solid #CCC;
1979 border:1px solid #CCC;
1978 color:blue;
1980 color:blue;
1979 margin-bottom:10px;
1981 margin-bottom:10px;
1980 padding:10px 0;
1982 padding:10px 0;
1981 }
1983 }
1982
1984
1983 .box .search div.search_path div.link {
1985 .box .search div.search_path div.link {
1984 font-weight:700;
1986 font-weight:700;
1985 margin-left:25px;
1987 margin-left:25px;
1986 }
1988 }
1987
1989
1988 .box .search div.search_path div.link a {
1990 .box .search div.search_path div.link a {
1989 color:#003367;
1991 color:#003367;
1990 cursor:pointer;
1992 cursor:pointer;
1991 text-decoration:none;
1993 text-decoration:none;
1992 }
1994 }
1993
1995
1994 #path_unlock {
1996 #path_unlock {
1995 color:red;
1997 color:red;
1996 font-size:1.2em;
1998 font-size:1.2em;
1997 padding-left:4px;
1999 padding-left:4px;
1998 }
2000 }
1999
2001
2000 .info_box span {
2002 .info_box span {
2001 margin-left:3px;
2003 margin-left:3px;
2002 margin-right:3px;
2004 margin-right:3px;
2003 }
2005 }
2004
2006
2005 .info_box .rev {
2007 .info_box .rev {
2006 color: #003367;
2008 color: #003367;
2007 font-size: 1.6em;
2009 font-size: 1.6em;
2008 font-weight: bold;
2010 font-weight: bold;
2009 vertical-align: sub;
2011 vertical-align: sub;
2010 }
2012 }
2011
2013
2012
2014
2013 .info_box input#at_rev,.info_box input#size {
2015 .info_box input#at_rev,.info_box input#size {
2014 background:#FFF;
2016 background:#FFF;
2015 border-top:1px solid #b3b3b3;
2017 border-top:1px solid #b3b3b3;
2016 border-left:1px solid #b3b3b3;
2018 border-left:1px solid #b3b3b3;
2017 border-right:1px solid #eaeaea;
2019 border-right:1px solid #eaeaea;
2018 border-bottom:1px solid #eaeaea;
2020 border-bottom:1px solid #eaeaea;
2019 color:#000;
2021 color:#000;
2020 font-family:Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
2022 font-family:Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
2021 font-size:12px;
2023 font-size:12px;
2022 margin:0;
2024 margin:0;
2023 padding:1px 5px 1px;
2025 padding:1px 5px 1px;
2024 }
2026 }
2025
2027
2026 .info_box input#view {
2028 .info_box input#view {
2027 text-align:center;
2029 text-align:center;
2028 padding:4px 3px 2px 2px;
2030 padding:4px 3px 2px 2px;
2029 }
2031 }
2030
2032
2031 .yui-overlay,.yui-panel-container {
2033 .yui-overlay,.yui-panel-container {
2032 visibility:hidden;
2034 visibility:hidden;
2033 position:absolute;
2035 position:absolute;
2034 z-index:2;
2036 z-index:2;
2035 }
2037 }
2036
2038
2037 .yui-tt {
2039 .yui-tt {
2038 visibility:hidden;
2040 visibility:hidden;
2039 position:absolute;
2041 position:absolute;
2040 color:#666;
2042 color:#666;
2041 background-color:#FFF;
2043 background-color:#FFF;
2042 font-family:arial, helvetica, verdana, sans-serif;
2044 font-family:arial, helvetica, verdana, sans-serif;
2043 border:2px solid #003367;
2045 border:2px solid #003367;
2044 font:100% sans-serif;
2046 font:100% sans-serif;
2045 width:auto;
2047 width:auto;
2046 opacity:1px;
2048 opacity:1px;
2047 padding:8px;
2049 padding:8px;
2048 white-space: pre-wrap;
2050 white-space: pre-wrap;
2049 -webkit-border-radius: 8px 8px 8px 8px;
2051 -webkit-border-radius: 8px 8px 8px 8px;
2050 -khtml-border-radius: 8px 8px 8px 8px;
2052 -khtml-border-radius: 8px 8px 8px 8px;
2051 -moz-border-radius: 8px 8px 8px 8px;
2053 -moz-border-radius: 8px 8px 8px 8px;
2052 border-radius: 8px 8px 8px 8px;
2054 border-radius: 8px 8px 8px 8px;
2053 box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
2055 box-shadow: 0 2px 2px rgba(0, 0, 0, 0.6);
2054 }
2056 }
2055
2057
2056 .ac {
2058 .ac {
2057 vertical-align:top;
2059 vertical-align:top;
2058 }
2060 }
2059
2061
2060 .ac .yui-ac {
2062 .ac .yui-ac {
2061 position:relative;
2063 position:relative;
2062 font-family:arial;
2064 font-family:arial;
2063 font-size:100%;
2065 font-size:100%;
2064 }
2066 }
2065
2067
2066 .ac .perm_ac {
2068 .ac .perm_ac {
2067 width:15em;
2069 width:15em;
2068 }
2070 }
2069
2071
2070 .ac .yui-ac-input {
2072 .ac .yui-ac-input {
2071 width:100%;
2073 width:100%;
2072 }
2074 }
2073
2075
2074 .ac .yui-ac-container {
2076 .ac .yui-ac-container {
2075 position:absolute;
2077 position:absolute;
2076 top:1.6em;
2078 top:1.6em;
2077 width:100%;
2079 width:100%;
2078 }
2080 }
2079
2081
2080 .ac .yui-ac-content {
2082 .ac .yui-ac-content {
2081 position:absolute;
2083 position:absolute;
2082 width:100%;
2084 width:100%;
2083 border:1px solid gray;
2085 border:1px solid gray;
2084 background:#fff;
2086 background:#fff;
2085 overflow:hidden;
2087 overflow:hidden;
2086 z-index:9050;
2088 z-index:9050;
2087 }
2089 }
2088
2090
2089 .ac .yui-ac-shadow {
2091 .ac .yui-ac-shadow {
2090 position:absolute;
2092 position:absolute;
2091 width:100%;
2093 width:100%;
2092 background:#000;
2094 background:#000;
2093 -moz-opacity:0.1px;
2095 -moz-opacity:0.1px;
2094 opacity:.10;
2096 opacity:.10;
2095 filter:alpha(opacity = 10);
2097 filter:alpha(opacity = 10);
2096 z-index:9049;
2098 z-index:9049;
2097 margin:.3em;
2099 margin:.3em;
2098 }
2100 }
2099
2101
2100 .ac .yui-ac-content ul {
2102 .ac .yui-ac-content ul {
2101 width:100%;
2103 width:100%;
2102 margin:0;
2104 margin:0;
2103 padding:0;
2105 padding:0;
2104 }
2106 }
2105
2107
2106 .ac .yui-ac-content li {
2108 .ac .yui-ac-content li {
2107 cursor:default;
2109 cursor:default;
2108 white-space:nowrap;
2110 white-space:nowrap;
2109 margin:0;
2111 margin:0;
2110 padding:2px 5px;
2112 padding:2px 5px;
2111 }
2113 }
2112
2114
2113 .ac .yui-ac-content li.yui-ac-prehighlight {
2115 .ac .yui-ac-content li.yui-ac-prehighlight {
2114 background:#B3D4FF;
2116 background:#B3D4FF;
2115 }
2117 }
2116
2118
2117 .ac .yui-ac-content li.yui-ac-highlight {
2119 .ac .yui-ac-content li.yui-ac-highlight {
2118 background:#556CB5;
2120 background:#556CB5;
2119 color:#FFF;
2121 color:#FFF;
2120 }
2122 }
2121
2123
2122
2124
2123 .follow{
2125 .follow{
2124 background:url("../images/icons/heart_add.png") no-repeat scroll 3px;
2126 background:url("../images/icons/heart_add.png") no-repeat scroll 3px;
2125 height: 16px;
2127 height: 16px;
2126 width: 20px;
2128 width: 20px;
2127 cursor: pointer;
2129 cursor: pointer;
2128 display: block;
2130 display: block;
2129 float: right;
2131 float: right;
2130 margin-top: 2px;
2132 margin-top: 2px;
2131 }
2133 }
2132
2134
2133 .following{
2135 .following{
2134 background:url("../images/icons/heart_delete.png") no-repeat scroll 3px;
2136 background:url("../images/icons/heart_delete.png") no-repeat scroll 3px;
2135 height: 16px;
2137 height: 16px;
2136 width: 20px;
2138 width: 20px;
2137 cursor: pointer;
2139 cursor: pointer;
2138 display: block;
2140 display: block;
2139 float: right;
2141 float: right;
2140 margin-top: 2px;
2142 margin-top: 2px;
2141 }
2143 }
2142
2144
2143 .currently_following{
2145 .currently_following{
2144 padding-left: 10px;
2146 padding-left: 10px;
2145 padding-bottom:5px;
2147 padding-bottom:5px;
2146 }
2148 }
2147
2149
2148 .add_icon {
2150 .add_icon {
2149 background:url("../images/icons/add.png") no-repeat scroll 3px;
2151 background:url("../images/icons/add.png") no-repeat scroll 3px;
2150 padding-left:20px;
2152 padding-left:20px;
2151 padding-top:0px;
2153 padding-top:0px;
2152 text-align:left;
2154 text-align:left;
2153 }
2155 }
2154
2156
2155 .edit_icon {
2157 .edit_icon {
2156 background:url("../images/icons/folder_edit.png") no-repeat scroll 3px;
2158 background:url("../images/icons/folder_edit.png") no-repeat scroll 3px;
2157 padding-left:20px;
2159 padding-left:20px;
2158 padding-top:0px;
2160 padding-top:0px;
2159 text-align:left;
2161 text-align:left;
2160 }
2162 }
2161
2163
2162 .delete_icon {
2164 .delete_icon {
2163 background:url("../images/icons/delete.png") no-repeat scroll 3px;
2165 background:url("../images/icons/delete.png") no-repeat scroll 3px;
2164 padding-left:20px;
2166 padding-left:20px;
2165 padding-top:0px;
2167 padding-top:0px;
2166 text-align:left;
2168 text-align:left;
2167 }
2169 }
2168
2170
2169 .refresh_icon {
2171 .refresh_icon {
2170 background:url("../images/icons/arrow_refresh.png") no-repeat scroll 3px;
2172 background:url("../images/icons/arrow_refresh.png") no-repeat scroll 3px;
2171 padding-left:20px;
2173 padding-left:20px;
2172 padding-top:0px;
2174 padding-top:0px;
2173 text-align:left;
2175 text-align:left;
2174 }
2176 }
2175
2177
2176 .pull_icon {
2178 .pull_icon {
2177 background:url("../images/icons/connect.png") no-repeat scroll 3px;
2179 background:url("../images/icons/connect.png") no-repeat scroll 3px;
2178 padding-left:20px;
2180 padding-left:20px;
2179 padding-top:0px;
2181 padding-top:0px;
2180 text-align:left;
2182 text-align:left;
2181 }
2183 }
2182
2184
2183 .rss_icon {
2185 .rss_icon {
2184 background:url("../images/icons/rss_16.png") no-repeat scroll 3px;
2186 background:url("../images/icons/rss_16.png") no-repeat scroll 3px;
2185 padding-left:20px;
2187 padding-left:20px;
2186 padding-top:0px;
2188 padding-top:0px;
2187 text-align:left;
2189 text-align:left;
2188 }
2190 }
2189
2191
2190 .atom_icon {
2192 .atom_icon {
2191 background:url("../images/icons/atom.png") no-repeat scroll 3px;
2193 background:url("../images/icons/atom.png") no-repeat scroll 3px;
2192 padding-left:20px;
2194 padding-left:20px;
2193 padding-top:0px;
2195 padding-top:0px;
2194 text-align:left;
2196 text-align:left;
2195 }
2197 }
2196
2198
2197 .archive_icon {
2199 .archive_icon {
2198 background:url("../images/icons/compress.png") no-repeat scroll 3px;
2200 background:url("../images/icons/compress.png") no-repeat scroll 3px;
2199 padding-left:20px;
2201 padding-left:20px;
2200 text-align:left;
2202 text-align:left;
2201 padding-top:1px;
2203 padding-top:1px;
2202 }
2204 }
2203
2205
2204 .start_following_icon {
2206 .start_following_icon {
2205 background:url("../images/icons/heart_add.png") no-repeat scroll 3px;
2207 background:url("../images/icons/heart_add.png") no-repeat scroll 3px;
2206 padding-left:20px;
2208 padding-left:20px;
2207 text-align:left;
2209 text-align:left;
2208 padding-top:0px;
2210 padding-top:0px;
2209 }
2211 }
2210
2212
2211 .stop_following_icon {
2213 .stop_following_icon {
2212 background:url("../images/icons/heart_delete.png") no-repeat scroll 3px;
2214 background:url("../images/icons/heart_delete.png") no-repeat scroll 3px;
2213 padding-left:20px;
2215 padding-left:20px;
2214 text-align:left;
2216 text-align:left;
2215 padding-top:0px;
2217 padding-top:0px;
2216 }
2218 }
2217
2219
2218 .action_button {
2220 .action_button {
2219 border:0;
2221 border:0;
2220 display:inline;
2222 display:inline;
2221 }
2223 }
2222
2224
2223 .action_button:hover {
2225 .action_button:hover {
2224 border:0;
2226 border:0;
2225 text-decoration:underline;
2227 text-decoration:underline;
2226 cursor:pointer;
2228 cursor:pointer;
2227 }
2229 }
2228
2230
2229 #switch_repos {
2231 #switch_repos {
2230 position:absolute;
2232 position:absolute;
2231 height:25px;
2233 height:25px;
2232 z-index:1;
2234 z-index:1;
2233 }
2235 }
2234
2236
2235 #switch_repos select {
2237 #switch_repos select {
2236 min-width:150px;
2238 min-width:150px;
2237 max-height:250px;
2239 max-height:250px;
2238 z-index:1;
2240 z-index:1;
2239 }
2241 }
2240
2242
2241 .breadcrumbs {
2243 .breadcrumbs {
2242 border:medium none;
2244 border:medium none;
2243 color:#FFF;
2245 color:#FFF;
2244 float:left;
2246 float:left;
2245 text-transform:uppercase;
2247 text-transform:uppercase;
2246 font-weight:700;
2248 font-weight:700;
2247 font-size:14px;
2249 font-size:14px;
2248 margin:0;
2250 margin:0;
2249 padding:11px 0 11px 10px;
2251 padding:11px 0 11px 10px;
2250 }
2252 }
2251
2253
2252 .breadcrumbs a {
2254 .breadcrumbs a {
2253 color:#FFF;
2255 color:#FFF;
2254 }
2256 }
2255
2257
2256 .flash_msg ul {
2258 .flash_msg ul {
2257 margin:0;
2259 margin:0;
2258 padding:0 0 10px;
2260 padding:0 0 10px;
2259 }
2261 }
2260
2262
2261 .error_msg {
2263 .error_msg {
2262 background-color:#FFCFCF;
2264 background-color:#FFCFCF;
2263 background-image:url("../images/icons/error_msg.png");
2265 background-image:url("../images/icons/error_msg.png");
2264 border:1px solid #FF9595;
2266 border:1px solid #FF9595;
2265 color:#C30;
2267 color:#C30;
2266 }
2268 }
2267
2269
2268 .warning_msg {
2270 .warning_msg {
2269 background-color:#FFFBCC;
2271 background-color:#FFFBCC;
2270 background-image:url("../images/icons/warning_msg.png");
2272 background-image:url("../images/icons/warning_msg.png");
2271 border:1px solid #FFF35E;
2273 border:1px solid #FFF35E;
2272 color:#C69E00;
2274 color:#C69E00;
2273 }
2275 }
2274
2276
2275 .success_msg {
2277 .success_msg {
2276 background-color:#D5FFCF;
2278 background-color:#D5FFCF;
2277 background-image:url("../images/icons/success_msg.png");
2279 background-image:url("../images/icons/success_msg.png");
2278 border:1px solid #97FF88;
2280 border:1px solid #97FF88;
2279 color:#090;
2281 color:#090;
2280 }
2282 }
2281
2283
2282 .notice_msg {
2284 .notice_msg {
2283 background-color:#DCE3FF;
2285 background-color:#DCE3FF;
2284 background-image:url("../images/icons/notice_msg.png");
2286 background-image:url("../images/icons/notice_msg.png");
2285 border:1px solid #93A8FF;
2287 border:1px solid #93A8FF;
2286 color:#556CB5;
2288 color:#556CB5;
2287 }
2289 }
2288
2290
2289 .success_msg,.error_msg,.notice_msg,.warning_msg {
2291 .success_msg,.error_msg,.notice_msg,.warning_msg {
2290 background-position:10px center;
2292 background-position:10px center;
2291 background-repeat:no-repeat;
2293 background-repeat:no-repeat;
2292 font-size:12px;
2294 font-size:12px;
2293 font-weight:700;
2295 font-weight:700;
2294 min-height:14px;
2296 min-height:14px;
2295 line-height:14px;
2297 line-height:14px;
2296 margin-bottom:0;
2298 margin-bottom:0;
2297 margin-top:0;
2299 margin-top:0;
2298 display:block;
2300 display:block;
2299 overflow:auto;
2301 overflow:auto;
2300 padding:6px 10px 6px 40px;
2302 padding:6px 10px 6px 40px;
2301 }
2303 }
2302
2304
2303 #msg_close {
2305 #msg_close {
2304 background:transparent url("../icons/cross_grey_small.png") no-repeat scroll 0 0;
2306 background:transparent url("../icons/cross_grey_small.png") no-repeat scroll 0 0;
2305 cursor:pointer;
2307 cursor:pointer;
2306 height:16px;
2308 height:16px;
2307 position:absolute;
2309 position:absolute;
2308 right:5px;
2310 right:5px;
2309 top:5px;
2311 top:5px;
2310 width:16px;
2312 width:16px;
2311 }
2313 }
2312
2314
2313 div#legend_container table,div#legend_choices table {
2315 div#legend_container table,div#legend_choices table {
2314 width:auto !important;
2316 width:auto !important;
2315 }
2317 }
2316
2318
2317 table#permissions_manage {
2319 table#permissions_manage {
2318 width:0 !important;
2320 width:0 !important;
2319 }
2321 }
2320
2322
2321 table#permissions_manage span.private_repo_msg {
2323 table#permissions_manage span.private_repo_msg {
2322 font-size:0.8em;
2324 font-size:0.8em;
2323 opacity:0.6px;
2325 opacity:0.6px;
2324 }
2326 }
2325
2327
2326 table#permissions_manage td.private_repo_msg {
2328 table#permissions_manage td.private_repo_msg {
2327 font-size:0.8em;
2329 font-size:0.8em;
2328 }
2330 }
2329
2331
2330 table#permissions_manage tr#add_perm_input td {
2332 table#permissions_manage tr#add_perm_input td {
2331 vertical-align:middle;
2333 vertical-align:middle;
2332 }
2334 }
2333
2335
2334 div.gravatar {
2336 div.gravatar {
2335 background-color:#FFF;
2337 background-color:#FFF;
2336 border:1px solid #D0D0D0;
2338 border:1px solid #D0D0D0;
2337 float:left;
2339 float:left;
2338 margin-right:0.7em;
2340 margin-right:0.7em;
2339 padding:2px 2px 0;
2341 padding:2px 2px 0;
2340
2342
2341 -webkit-border-radius: 6px;
2343 -webkit-border-radius: 6px;
2342 -khtml-border-radius: 6px;
2344 -khtml-border-radius: 6px;
2343 -moz-border-radius: 6px;
2345 -moz-border-radius: 6px;
2344 border-radius: 6px;
2346 border-radius: 6px;
2345
2347
2346 }
2348 }
2347
2349
2348 div.gravatar img {
2350 div.gravatar img {
2349 -webkit-border-radius: 4px;
2351 -webkit-border-radius: 4px;
2350 -khtml-border-radius: 4px;
2352 -khtml-border-radius: 4px;
2351 -moz-border-radius: 4px;
2353 -moz-border-radius: 4px;
2352 border-radius: 4px;
2354 border-radius: 4px;
2353 }
2355 }
2354
2356
2355 #header,#content,#footer {
2357 #header,#content,#footer {
2356 min-width:978px;
2358 min-width:978px;
2357 }
2359 }
2358
2360
2359 #content {
2361 #content {
2360 clear:both;
2362 clear:both;
2361 overflow:hidden;
2363 overflow:hidden;
2362 padding:14px 10px;
2364 padding:14px 10px;
2363 }
2365 }
2364
2366
2365 #content div.box div.title div.search {
2367 #content div.box div.title div.search {
2366 background:url("../images/title_link.png") no-repeat top left;
2368 background:url("../images/title_link.png") no-repeat top left;
2367 border-left:1px solid #316293;
2369 border-left:1px solid #316293;
2368 }
2370 }
2369
2371
2370 #content div.box div.title div.search div.input input {
2372 #content div.box div.title div.search div.input input {
2371 border:1px solid #316293;
2373 border:1px solid #316293;
2372 }
2374 }
2373
2375
2374
2376 .ui-button-small a:hover {
2375 input.ui-button-small {
2377
2378 }
2379 input.ui-button-small,.ui-button-small {
2376 background:#e5e3e3 url("../images/button.png") repeat-x !important;
2380 background:#e5e3e3 url("../images/button.png") repeat-x !important;
2377 border-top:1px solid #DDD !important;
2381 border-top:1px solid #DDD !important;
2378 border-left:1px solid #c6c6c6 !important;
2382 border-left:1px solid #c6c6c6 !important;
2379 border-right:1px solid #DDD !important;
2383 border-right:1px solid #DDD !important;
2380 border-bottom:1px solid #c6c6c6 !important;
2384 border-bottom:1px solid #c6c6c6 !important;
2381 color:#515151 !important;
2385 color:#515151 !important;
2382 outline:none !important;
2386 outline:none !important;
2383 margin:0 !important;
2387 margin:0 !important;
2384 -webkit-border-radius: 4px 4px 4px 4px !important;
2388 -webkit-border-radius: 4px 4px 4px 4px !important;
2385 -khtml-border-radius: 4px 4px 4px 4px !important;
2389 -khtml-border-radius: 4px 4px 4px 4px !important;
2386 -moz-border-radius: 4px 4px 4px 4px !important;
2390 -moz-border-radius: 4px 4px 4px 4px !important;
2387 border-radius: 4px 4px 4px 4px !important;
2391 border-radius: 4px 4px 4px 4px !important;
2388 box-shadow: 0 1px 0 #ececec !important;
2392 box-shadow: 0 1px 0 #ececec !important;
2389 cursor: pointer !important;
2393 cursor: pointer !important;
2390 }
2394 padding:0px 2px 1px 2px;
2391
2395 }
2392 input.ui-button-small:hover {
2396
2397 input.ui-button-small:hover,.ui-button-small:hover {
2393 background:#b4b4b4 url("../images/button_selected.png") repeat-x !important;
2398 background:#b4b4b4 url("../images/button_selected.png") repeat-x !important;
2394 border-top:1px solid #ccc !important;
2399 border-top:1px solid #ccc !important;
2395 border-left:1px solid #bebebe !important;
2400 border-left:1px solid #bebebe !important;
2396 border-right:1px solid #b1b1b1 !important;
2401 border-right:1px solid #b1b1b1 !important;
2397 border-bottom:1px solid #afafaf !important;
2402 border-bottom:1px solid #afafaf !important;
2398 }
2403 text-decoration: none;
2399
2404 }
2400 input.ui-button-small-blue {
2405
2406 input.ui-button-small-blue,.ui-button-small-blue {
2401 background:#4e85bb url("../images/button_highlight.png") repeat-x;
2407 background:#4e85bb url("../images/button_highlight.png") repeat-x;
2402 border-top:1px solid #5c91a4;
2408 border-top:1px solid #5c91a4;
2403 border-left:1px solid #2a6f89;
2409 border-left:1px solid #2a6f89;
2404 border-right:1px solid #2b7089;
2410 border-right:1px solid #2b7089;
2405 border-bottom:1px solid #1a6480;
2411 border-bottom:1px solid #1a6480;
2406 color:#fff;
2412 color:#fff;
2407 -webkit-border-radius: 4px 4px 4px 4px;
2413 -webkit-border-radius: 4px 4px 4px 4px;
2408 -khtml-border-radius: 4px 4px 4px 4px;
2414 -khtml-border-radius: 4px 4px 4px 4px;
2409 -moz-border-radius: 4px 4px 4px 4px;
2415 -moz-border-radius: 4px 4px 4px 4px;
2410 border-radius: 4px 4px 4px 4px;
2416 border-radius: 4px 4px 4px 4px;
2411 box-shadow: 0 1px 0 #ececec;
2417 box-shadow: 0 1px 0 #ececec;
2412 cursor: pointer;
2418 cursor: pointer;
2419 padding:0px 2px 1px 2px;
2413 }
2420 }
2414
2421
2415 input.ui-button-small-blue:hover {
2422 input.ui-button-small-blue:hover {
2416
2423
2417 }
2424 }
2418
2425
2419
2426
2420 ins,div.options a:hover {
2427 ins,div.options a:hover {
2421 text-decoration:none;
2428 text-decoration:none;
2422 }
2429 }
2423
2430
2424 img,#header #header-inner #quick li a:hover span.normal,#header #header-inner #quick li ul li.last,#content div.box div.form div.fields div.field div.textarea table td table td a,#clone_url {
2431 img,#header #header-inner #quick li a:hover span.normal,#header #header-inner #quick li ul li.last,#content div.box div.form div.fields div.field div.textarea table td table td a,#clone_url {
2425 border:none;
2432 border:none;
2426 }
2433 }
2427
2434
2428 img.icon,.right .merge img {
2435 img.icon,.right .merge img {
2429 vertical-align:bottom;
2436 vertical-align:bottom;
2430 }
2437 }
2431
2438
2432 #header ul#logged-user,#content div.box div.title ul.links,#content div.box div.message div.dismiss,#content div.box div.traffic div.legend ul {
2439 #header ul#logged-user,#content div.box div.title ul.links,#content div.box div.message div.dismiss,#content div.box div.traffic div.legend ul {
2433 float:right;
2440 float:right;
2434 margin:0;
2441 margin:0;
2435 padding:0;
2442 padding:0;
2436 }
2443 }
2437
2444
2438
2445
2439 #header #header-inner #home,#header #header-inner #logo,#content div.box ul.left,#content div.box ol.left,#content div.box div.pagination-left,div#commit_history,div#legend_data,div#legend_container,div#legend_choices {
2446 #header #header-inner #home,#header #header-inner #logo,#content div.box ul.left,#content div.box ol.left,#content div.box div.pagination-left,div#commit_history,div#legend_data,div#legend_container,div#legend_choices {
2440 float:left;
2447 float:left;
2441 }
2448 }
2442
2449
2443 #header #header-inner #quick li:hover ul ul,#header #header-inner #quick li:hover ul ul ul,#header #header-inner #quick li:hover ul ul ul ul,#content #left #menu ul.closed,#content #left #menu li ul.collapsed,.yui-tt-shadow {
2450 #header #header-inner #quick li:hover ul ul,#header #header-inner #quick li:hover ul ul ul,#header #header-inner #quick li:hover ul ul ul ul,#content #left #menu ul.closed,#content #left #menu li ul.collapsed,.yui-tt-shadow {
2444 display:none;
2451 display:none;
2445 }
2452 }
2446
2453
2447 #header #header-inner #quick li:hover ul,#header #header-inner #quick li li:hover ul,#header #header-inner #quick li li li:hover ul,#header #header-inner #quick li li li li:hover ul,#content #left #menu ul.opened,#content #left #menu li ul.expanded {
2454 #header #header-inner #quick li:hover ul,#header #header-inner #quick li li:hover ul,#header #header-inner #quick li li li:hover ul,#header #header-inner #quick li li li li:hover ul,#content #left #menu ul.opened,#content #left #menu li ul.expanded {
2448 display:block;
2455 display:block;
2449 }
2456 }
2450
2457
2451 #content div.graph{
2458 #content div.graph{
2452 padding:0 10px 10px;
2459 padding:0 10px 10px;
2453 }
2460 }
2454
2461
2455 #content div.box div.title ul.links li a:hover,#content div.box div.title ul.links li.ui-tabs-selected a {
2462 #content div.box div.title ul.links li a:hover,#content div.box div.title ul.links li.ui-tabs-selected a {
2456 color:#bfe3ff;
2463 color:#bfe3ff;
2457 }
2464 }
2458
2465
2459 #content div.box ol.lower-roman,#content div.box ol.upper-roman,#content div.box ol.lower-alpha,#content div.box ol.upper-alpha,#content div.box ol.decimal {
2466 #content div.box ol.lower-roman,#content div.box ol.upper-roman,#content div.box ol.lower-alpha,#content div.box ol.upper-alpha,#content div.box ol.decimal {
2460 margin:10px 24px 10px 44px;
2467 margin:10px 24px 10px 44px;
2461 }
2468 }
2462
2469
2463 #content div.box div.form,#content div.box div.table,#content div.box div.traffic {
2470 #content div.box div.form,#content div.box div.table,#content div.box div.traffic {
2464 clear:both;
2471 clear:both;
2465 overflow:hidden;
2472 overflow:hidden;
2466 margin:0;
2473 margin:0;
2467 padding:0 20px 10px;
2474 padding:0 20px 10px;
2468 }
2475 }
2469
2476
2470 #content div.box div.form div.fields,#login div.form,#login div.form div.fields,#register div.form,#register div.form div.fields {
2477 #content div.box div.form div.fields,#login div.form,#login div.form div.fields,#register div.form,#register div.form div.fields {
2471 clear:both;
2478 clear:both;
2472 overflow:hidden;
2479 overflow:hidden;
2473 margin:0;
2480 margin:0;
2474 padding:0;
2481 padding:0;
2475 }
2482 }
2476
2483
2477 #content div.box div.form div.fields div.field div.label span,#login div.form div.fields div.field div.label span,#register div.form div.fields div.field div.label span {
2484 #content div.box div.form div.fields div.field div.label span,#login div.form div.fields div.field div.label span,#register div.form div.fields div.field div.label span {
2478 height:1%;
2485 height:1%;
2479 display:block;
2486 display:block;
2480 color:#363636;
2487 color:#363636;
2481 margin:0;
2488 margin:0;
2482 padding:2px 0 0;
2489 padding:2px 0 0;
2483 }
2490 }
2484
2491
2485 #content div.box div.form div.fields div.field div.input input.error,#login div.form div.fields div.field div.input input.error,#register div.form div.fields div.field div.input input.error {
2492 #content div.box div.form div.fields div.field div.input input.error,#login div.form div.fields div.field div.input input.error,#register div.form div.fields div.field div.input input.error {
2486 background:#FBE3E4;
2493 background:#FBE3E4;
2487 border-top:1px solid #e1b2b3;
2494 border-top:1px solid #e1b2b3;
2488 border-left:1px solid #e1b2b3;
2495 border-left:1px solid #e1b2b3;
2489 border-right:1px solid #FBC2C4;
2496 border-right:1px solid #FBC2C4;
2490 border-bottom:1px solid #FBC2C4;
2497 border-bottom:1px solid #FBC2C4;
2491 }
2498 }
2492
2499
2493 #content div.box div.form div.fields div.field div.input input.success,#login div.form div.fields div.field div.input input.success,#register div.form div.fields div.field div.input input.success {
2500 #content div.box div.form div.fields div.field div.input input.success,#login div.form div.fields div.field div.input input.success,#register div.form div.fields div.field div.input input.success {
2494 background:#E6EFC2;
2501 background:#E6EFC2;
2495 border-top:1px solid #cebb98;
2502 border-top:1px solid #cebb98;
2496 border-left:1px solid #cebb98;
2503 border-left:1px solid #cebb98;
2497 border-right:1px solid #c6d880;
2504 border-right:1px solid #c6d880;
2498 border-bottom:1px solid #c6d880;
2505 border-bottom:1px solid #c6d880;
2499 }
2506 }
2500
2507
2501 #content div.box-left div.form div.fields div.field div.textarea,#content div.box-right div.form div.fields div.field div.textarea,#content div.box div.form div.fields div.field div.select select,#content div.box table th.selected input,#content div.box table td.selected input {
2508 #content div.box-left div.form div.fields div.field div.textarea,#content div.box-right div.form div.fields div.field div.textarea,#content div.box div.form div.fields div.field div.select select,#content div.box table th.selected input,#content div.box table td.selected input {
2502 margin:0;
2509 margin:0;
2503 }
2510 }
2504
2511
2505 #content div.box-left div.form div.fields div.field div.select,#content div.box-left div.form div.fields div.field div.checkboxes,#content div.box-left div.form div.fields div.field div.radios,#content div.box-right div.form div.fields div.field div.select,#content div.box-right div.form div.fields div.field div.checkboxes,#content div.box-right div.form div.fields div.field div.radios{
2512 #content div.box-left div.form div.fields div.field div.select,#content div.box-left div.form div.fields div.field div.checkboxes,#content div.box-left div.form div.fields div.field div.radios,#content div.box-right div.form div.fields div.field div.select,#content div.box-right div.form div.fields div.field div.checkboxes,#content div.box-right div.form div.fields div.field div.radios{
2506 margin:0 0 0 0px !important;
2513 margin:0 0 0 0px !important;
2507 padding:0;
2514 padding:0;
2508 }
2515 }
2509
2516
2510 #content div.box div.form div.fields div.field div.select,#content div.box div.form div.fields div.field div.checkboxes,#content div.box div.form div.fields div.field div.radios {
2517 #content div.box div.form div.fields div.field div.select,#content div.box div.form div.fields div.field div.checkboxes,#content div.box div.form div.fields div.field div.radios {
2511 margin:0 0 0 200px;
2518 margin:0 0 0 200px;
2512 padding:0;
2519 padding:0;
2513 }
2520 }
2514
2521
2515
2522
2516 #content div.box div.form div.fields div.field div.select a:hover,#content div.box div.form div.fields div.field div.select a.ui-selectmenu:hover,#content div.box div.action a:hover {
2523 #content div.box div.form div.fields div.field div.select a:hover,#content div.box div.form div.fields div.field div.select a.ui-selectmenu:hover,#content div.box div.action a:hover {
2517 color:#000;
2524 color:#000;
2518 text-decoration:none;
2525 text-decoration:none;
2519 }
2526 }
2520
2527
2521 #content div.box div.form div.fields div.field div.select a.ui-selectmenu-focus,#content div.box div.action a.ui-selectmenu-focus {
2528 #content div.box div.form div.fields div.field div.select a.ui-selectmenu-focus,#content div.box div.action a.ui-selectmenu-focus {
2522 border:1px solid #666;
2529 border:1px solid #666;
2523 }
2530 }
2524
2531
2525 #content div.box div.form div.fields div.field div.checkboxes div.checkbox,#content div.box div.form div.fields div.field div.radios div.radio {
2532 #content div.box div.form div.fields div.field div.checkboxes div.checkbox,#content div.box div.form div.fields div.field div.radios div.radio {
2526 clear:both;
2533 clear:both;
2527 overflow:hidden;
2534 overflow:hidden;
2528 margin:0;
2535 margin:0;
2529 padding:8px 0 2px;
2536 padding:8px 0 2px;
2530 }
2537 }
2531
2538
2532 #content div.box div.form div.fields div.field div.checkboxes div.checkbox input,#content div.box div.form div.fields div.field div.radios div.radio input {
2539 #content div.box div.form div.fields div.field div.checkboxes div.checkbox input,#content div.box div.form div.fields div.field div.radios div.radio input {
2533 float:left;
2540 float:left;
2534 margin:0;
2541 margin:0;
2535 }
2542 }
2536
2543
2537 #content div.box div.form div.fields div.field div.checkboxes div.checkbox label,#content div.box div.form div.fields div.field div.radios div.radio label {
2544 #content div.box div.form div.fields div.field div.checkboxes div.checkbox label,#content div.box div.form div.fields div.field div.radios div.radio label {
2538 height:1%;
2545 height:1%;
2539 display:block;
2546 display:block;
2540 float:left;
2547 float:left;
2541 margin:2px 0 0 4px;
2548 margin:2px 0 0 4px;
2542 }
2549 }
2543
2550
2544 div.form div.fields div.field div.button input,#content div.box div.form div.fields div.buttons input,div.form div.fields div.buttons input,#content div.box div.action div.button input {
2551 div.form div.fields div.field div.button input,#content div.box div.form div.fields div.buttons input,div.form div.fields div.buttons input,#content div.box div.action div.button input {
2545 color:#000;
2552 color:#000;
2546 font-family:Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
2553 font-family:Lucida Grande, Verdana, Lucida Sans Regular, Lucida Sans Unicode, Arial, sans-serif;
2547 font-size:11px;
2554 font-size:11px;
2548 font-weight:700;
2555 font-weight:700;
2549 margin:0;
2556 margin:0;
2550 }
2557 }
2551
2558
2552 input.ui-button {
2559 input.ui-button {
2553 background:#e5e3e3 url("../images/button.png") repeat-x;
2560 background:#e5e3e3 url("../images/button.png") repeat-x;
2554 border-top:1px solid #DDD;
2561 border-top:1px solid #DDD;
2555 border-left:1px solid #c6c6c6;
2562 border-left:1px solid #c6c6c6;
2556 border-right:1px solid #DDD;
2563 border-right:1px solid #DDD;
2557 border-bottom:1px solid #c6c6c6;
2564 border-bottom:1px solid #c6c6c6;
2558 color:#515151 !important;
2565 color:#515151 !important;
2559 outline:none;
2566 outline:none;
2560 margin:0;
2567 margin:0;
2561 padding:6px 12px;
2568 padding:6px 12px;
2562 -webkit-border-radius: 4px 4px 4px 4px;
2569 -webkit-border-radius: 4px 4px 4px 4px;
2563 -khtml-border-radius: 4px 4px 4px 4px;
2570 -khtml-border-radius: 4px 4px 4px 4px;
2564 -moz-border-radius: 4px 4px 4px 4px;
2571 -moz-border-radius: 4px 4px 4px 4px;
2565 border-radius: 4px 4px 4px 4px;
2572 border-radius: 4px 4px 4px 4px;
2566 box-shadow: 0 1px 0 #ececec;
2573 box-shadow: 0 1px 0 #ececec;
2567 cursor: pointer;
2574 cursor: pointer;
2568 }
2575 }
2569
2576
2570 input.ui-button:hover {
2577 input.ui-button:hover {
2571 background:#b4b4b4 url("../images/button_selected.png") repeat-x;
2578 background:#b4b4b4 url("../images/button_selected.png") repeat-x;
2572 border-top:1px solid #ccc;
2579 border-top:1px solid #ccc;
2573 border-left:1px solid #bebebe;
2580 border-left:1px solid #bebebe;
2574 border-right:1px solid #b1b1b1;
2581 border-right:1px solid #b1b1b1;
2575 border-bottom:1px solid #afafaf;
2582 border-bottom:1px solid #afafaf;
2576 }
2583 }
2577
2584
2578 div.form div.fields div.field div.highlight,#content div.box div.form div.fields div.buttons div.highlight {
2585 div.form div.fields div.field div.highlight,#content div.box div.form div.fields div.buttons div.highlight {
2579 display:inline;
2586 display:inline;
2580 }
2587 }
2581
2588
2582 #content div.box div.form div.fields div.buttons,div.form div.fields div.buttons {
2589 #content div.box div.form div.fields div.buttons,div.form div.fields div.buttons {
2583 margin:10px 0 0 200px;
2590 margin:10px 0 0 200px;
2584 padding:0;
2591 padding:0;
2585 }
2592 }
2586
2593
2587 #content div.box-left div.form div.fields div.buttons,#content div.box-right div.form div.fields div.buttons,div.box-left div.form div.fields div.buttons,div.box-right div.form div.fields div.buttons {
2594 #content div.box-left div.form div.fields div.buttons,#content div.box-right div.form div.fields div.buttons,div.box-left div.form div.fields div.buttons,div.box-right div.form div.fields div.buttons {
2588 margin:10px 0 0;
2595 margin:10px 0 0;
2589 }
2596 }
2590
2597
2591 #content div.box table td.user,#content div.box table td.address {
2598 #content div.box table td.user,#content div.box table td.address {
2592 width:10%;
2599 width:10%;
2593 text-align:center;
2600 text-align:center;
2594 }
2601 }
2595
2602
2596 #content div.box div.action div.button,#login div.form div.fields div.field div.input div.link,#register div.form div.fields div.field div.input div.link {
2603 #content div.box div.action div.button,#login div.form div.fields div.field div.input div.link,#register div.form div.fields div.field div.input div.link {
2597 text-align:right;
2604 text-align:right;
2598 margin:6px 0 0;
2605 margin:6px 0 0;
2599 padding:0;
2606 padding:0;
2600 }
2607 }
2601
2608
2602
2609
2603 #content div.box div.action div.button input.ui-state-hover,#login div.form div.fields div.buttons input.ui-state-hover,#register div.form div.fields div.buttons input.ui-state-hover {
2610 #content div.box div.action div.button input.ui-state-hover,#login div.form div.fields div.buttons input.ui-state-hover,#register div.form div.fields div.buttons input.ui-state-hover {
2604 background:#b4b4b4 url("../images/button_selected.png") repeat-x;
2611 background:#b4b4b4 url("../images/button_selected.png") repeat-x;
2605 border-top:1px solid #ccc;
2612 border-top:1px solid #ccc;
2606 border-left:1px solid #bebebe;
2613 border-left:1px solid #bebebe;
2607 border-right:1px solid #b1b1b1;
2614 border-right:1px solid #b1b1b1;
2608 border-bottom:1px solid #afafaf;
2615 border-bottom:1px solid #afafaf;
2609 color:#515151;
2616 color:#515151;
2610 margin:0;
2617 margin:0;
2611 padding:6px 12px;
2618 padding:6px 12px;
2612 }
2619 }
2613
2620
2614 #content div.box div.pagination div.results,#content div.box div.pagination-wh div.results {
2621 #content div.box div.pagination div.results,#content div.box div.pagination-wh div.results {
2615 text-align:left;
2622 text-align:left;
2616 float:left;
2623 float:left;
2617 margin:0;
2624 margin:0;
2618 padding:0;
2625 padding:0;
2619 }
2626 }
2620
2627
2621 #content div.box div.pagination div.results span,#content div.box div.pagination-wh div.results span {
2628 #content div.box div.pagination div.results span,#content div.box div.pagination-wh div.results span {
2622 height:1%;
2629 height:1%;
2623 display:block;
2630 display:block;
2624 float:left;
2631 float:left;
2625 background:#ebebeb url("../images/pager.png") repeat-x;
2632 background:#ebebeb url("../images/pager.png") repeat-x;
2626 border-top:1px solid #dedede;
2633 border-top:1px solid #dedede;
2627 border-left:1px solid #cfcfcf;
2634 border-left:1px solid #cfcfcf;
2628 border-right:1px solid #c4c4c4;
2635 border-right:1px solid #c4c4c4;
2629 border-bottom:1px solid #c4c4c4;
2636 border-bottom:1px solid #c4c4c4;
2630 color:#4A4A4A;
2637 color:#4A4A4A;
2631 font-weight:700;
2638 font-weight:700;
2632 margin:0;
2639 margin:0;
2633 padding:6px 8px;
2640 padding:6px 8px;
2634 }
2641 }
2635
2642
2636 #content div.box div.pagination ul.pager li.disabled,#content div.box div.pagination-wh a.disabled {
2643 #content div.box div.pagination ul.pager li.disabled,#content div.box div.pagination-wh a.disabled {
2637 color:#B4B4B4;
2644 color:#B4B4B4;
2638 padding:6px;
2645 padding:6px;
2639 }
2646 }
2640
2647
2641 #login,#register {
2648 #login,#register {
2642 width:520px;
2649 width:520px;
2643 margin:10% auto 0;
2650 margin:10% auto 0;
2644 padding:0;
2651 padding:0;
2645 }
2652 }
2646
2653
2647 #login div.color,#register div.color {
2654 #login div.color,#register div.color {
2648 clear:both;
2655 clear:both;
2649 overflow:hidden;
2656 overflow:hidden;
2650 background:#FFF;
2657 background:#FFF;
2651 margin:10px auto 0;
2658 margin:10px auto 0;
2652 padding:3px 3px 3px 0;
2659 padding:3px 3px 3px 0;
2653 }
2660 }
2654
2661
2655 #login div.color a,#register div.color a {
2662 #login div.color a,#register div.color a {
2656 width:20px;
2663 width:20px;
2657 height:20px;
2664 height:20px;
2658 display:block;
2665 display:block;
2659 float:left;
2666 float:left;
2660 margin:0 0 0 3px;
2667 margin:0 0 0 3px;
2661 padding:0;
2668 padding:0;
2662 }
2669 }
2663
2670
2664 #login div.title h5,#register div.title h5 {
2671 #login div.title h5,#register div.title h5 {
2665 color:#fff;
2672 color:#fff;
2666 margin:10px;
2673 margin:10px;
2667 padding:0;
2674 padding:0;
2668 }
2675 }
2669
2676
2670 #login div.form div.fields div.field,#register div.form div.fields div.field {
2677 #login div.form div.fields div.field,#register div.form div.fields div.field {
2671 clear:both;
2678 clear:both;
2672 overflow:hidden;
2679 overflow:hidden;
2673 margin:0;
2680 margin:0;
2674 padding:0 0 10px;
2681 padding:0 0 10px;
2675 }
2682 }
2676
2683
2677 #login div.form div.fields div.field span.error-message,#register div.form div.fields div.field span.error-message {
2684 #login div.form div.fields div.field span.error-message,#register div.form div.fields div.field span.error-message {
2678 height:1%;
2685 height:1%;
2679 display:block;
2686 display:block;
2680 color:red;
2687 color:red;
2681 margin:8px 0 0;
2688 margin:8px 0 0;
2682 padding:0;
2689 padding:0;
2683 max-width: 320px;
2690 max-width: 320px;
2684 }
2691 }
2685
2692
2686 #login div.form div.fields div.field div.label label,#register div.form div.fields div.field div.label label {
2693 #login div.form div.fields div.field div.label label,#register div.form div.fields div.field div.label label {
2687 color:#000;
2694 color:#000;
2688 font-weight:700;
2695 font-weight:700;
2689 }
2696 }
2690
2697
2691 #login div.form div.fields div.field div.input,#register div.form div.fields div.field div.input {
2698 #login div.form div.fields div.field div.input,#register div.form div.fields div.field div.input {
2692 float:left;
2699 float:left;
2693 margin:0;
2700 margin:0;
2694 padding:0;
2701 padding:0;
2695 }
2702 }
2696
2703
2697 #login div.form div.fields div.field div.checkbox,#register div.form div.fields div.field div.checkbox {
2704 #login div.form div.fields div.field div.checkbox,#register div.form div.fields div.field div.checkbox {
2698 margin:0 0 0 184px;
2705 margin:0 0 0 184px;
2699 padding:0;
2706 padding:0;
2700 }
2707 }
2701
2708
2702 #login div.form div.fields div.field div.checkbox label,#register div.form div.fields div.field div.checkbox label {
2709 #login div.form div.fields div.field div.checkbox label,#register div.form div.fields div.field div.checkbox label {
2703 color:#565656;
2710 color:#565656;
2704 font-weight:700;
2711 font-weight:700;
2705 }
2712 }
2706
2713
2707 #login div.form div.fields div.buttons input,#register div.form div.fields div.buttons input {
2714 #login div.form div.fields div.buttons input,#register div.form div.fields div.buttons input {
2708 color:#000;
2715 color:#000;
2709 font-size:1em;
2716 font-size:1em;
2710 font-weight:700;
2717 font-weight:700;
2711 font-family:Verdana, Helvetica, Sans-Serif;
2718 font-family:Verdana, Helvetica, Sans-Serif;
2712 margin:0;
2719 margin:0;
2713 }
2720 }
2714
2721
2715 #changeset_content .container .wrapper,#graph_content .container .wrapper {
2722 #changeset_content .container .wrapper,#graph_content .container .wrapper {
2716 width:600px;
2723 width:600px;
2717 }
2724 }
2718
2725
2719 #changeset_content .container .left,#graph_content .container .left {
2726 #changeset_content .container .left,#graph_content .container .left {
2720 float:left;
2727 float:left;
2721 width:70%;
2728 width:70%;
2722 padding-left:5px;
2729 padding-left:5px;
2723 }
2730 }
2724
2731
2725 #changeset_content .container .left .date,.ac .match {
2732 #changeset_content .container .left .date,.ac .match {
2726 font-weight:700;
2733 font-weight:700;
2727 padding-top: 5px;
2734 padding-top: 5px;
2728 padding-bottom:5px;
2735 padding-bottom:5px;
2729 }
2736 }
2730
2737
2731 div#legend_container table td,div#legend_choices table td {
2738 div#legend_container table td,div#legend_choices table td {
2732 border:none !important;
2739 border:none !important;
2733 height:20px !important;
2740 height:20px !important;
2734 padding:0 !important;
2741 padding:0 !important;
2735 }
2742 }
2736
2743
2737 #q_filter{
2744 #q_filter{
2738 border:0 none;
2745 border:0 none;
2739 color:#AAAAAA;
2746 color:#AAAAAA;
2740 margin-bottom:-4px;
2747 margin-bottom:-4px;
2741 margin-top:-4px;
2748 margin-top:-4px;
2742 padding-left:3px;
2749 padding-left:3px;
2743 }
2750 }
2744
2751
2745 #node_filter{
2752 #node_filter{
2746 border:0px solid #545454;
2753 border:0px solid #545454;
2747 color:#AAAAAA;
2754 color:#AAAAAA;
2748 padding-left:3px;
2755 padding-left:3px;
2749 }
2756 }
@@ -1,21 +1,23
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.html"/>
2 <%inherit file="/base/base.html"/>
3 <%def name="title()">
3 <%def name="title()">
4 ${_('Repository group')} - ${c.rhodecode_name}
4 ${_('Repository group')} - ${c.rhodecode_name}
5 </%def>
5 </%def>
6
6
7 <%def name="breadcrumbs()">
7 <%def name="breadcrumbs()">
8 <span class="groups_breadcrumbs">
8 ${_('Groups')}
9 ${_('Groups')}
9 %if c.group.parent_group:
10 %if c.group.parent_group:
10 &raquo; ${h.link_to(c.group.parent_group.group_name,
11 &raquo; ${h.link_to(c.group.parent_group.name,
11 h.url('repos_group',id=c.group.parent_group.group_id))}
12 h.url('repos_group_home',group_name=c.group.parent_group.group_name))}
12 %endif
13 %endif
13 &raquo; "${c.group.group_name}" ${_('with')}
14 &raquo; "${c.group.name}" ${_('with')}
15 </span>
14 </%def>
16 </%def>
15
17
16 <%def name="page_nav()">
18 <%def name="page_nav()">
17 ${self.menu('admin')}
19 ${self.menu('admin')}
18 </%def>
20 </%def>
19 <%def name="main()">
21 <%def name="main()">
20 <%include file="/index_base.html" args="parent=self"/>
22 <%include file="/index_base.html" args="parent=self"/>
21 </%def>
23 </%def>
@@ -1,64 +1,64
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.html"/>
2 <%inherit file="/base/base.html"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('Edit repos group')} ${c.repos_group.group_name} - ${c.rhodecode_name}
5 ${_('Edit repos group')} ${c.repos_group.name} - ${c.rhodecode_name}
6 </%def>
6 </%def>
7 <%def name="breadcrumbs_links()">
7 <%def name="breadcrumbs_links()">
8 ${h.link_to(_('Admin'),h.url('admin_home'))}
8 ${h.link_to(_('Admin'),h.url('admin_home'))}
9 &raquo;
9 &raquo;
10 ${h.link_to(_('Repos groups'),h.url('repos_groups'))}
10 ${h.link_to(_('Repos groups'),h.url('repos_groups'))}
11 &raquo;
11 &raquo;
12 ${_('edit repos group')} "${c.repos_group.group_name}"
12 ${_('edit repos group')} "${c.repos_group.name}"
13 </%def>
13 </%def>
14
14
15 <%def name="page_nav()">
15 <%def name="page_nav()">
16 ${self.menu('admin')}
16 ${self.menu('admin')}
17 </%def>
17 </%def>
18
18
19 <%def name="main()">
19 <%def name="main()">
20 <div class="box">
20 <div class="box">
21 <!-- box / title -->
21 <!-- box / title -->
22 <div class="title">
22 <div class="title">
23 ${self.breadcrumbs()}
23 ${self.breadcrumbs()}
24 </div>
24 </div>
25 <!-- end box / title -->
25 <!-- end box / title -->
26 ${h.form(url('repos_group',id=c.repos_group.group_id),method='put')}
26 ${h.form(url('repos_group',id=c.repos_group.group_id),method='put')}
27 <div class="form">
27 <div class="form">
28 <!-- fields -->
28 <!-- fields -->
29 <div class="fields">
29 <div class="fields">
30 <div class="field">
30 <div class="field">
31 <div class="label">
31 <div class="label">
32 <label for="users_group_name">${_('Group name')}:</label>
32 <label for="users_group_name">${_('Group name')}:</label>
33 </div>
33 </div>
34 <div class="input">
34 <div class="input">
35 ${h.text('group_name',class_='medium')}
35 ${h.text('group_name',class_='medium')}
36 </div>
36 </div>
37 </div>
37 </div>
38
38
39 <div class="field">
39 <div class="field">
40 <div class="label label-textarea">
40 <div class="label label-textarea">
41 <label for="description">${_('Description')}:</label>
41 <label for="description">${_('Description')}:</label>
42 </div>
42 </div>
43 <div class="textarea text-area editor">
43 <div class="textarea text-area editor">
44 ${h.textarea('group_description',cols=23,rows=5,class_="medium")}
44 ${h.textarea('group_description',cols=23,rows=5,class_="medium")}
45 </div>
45 </div>
46 </div>
46 </div>
47
47
48 <div class="field">
48 <div class="field">
49 <div class="label">
49 <div class="label">
50 <label for="repo_group">${_('Group parent')}:</label>
50 <label for="repo_group">${_('Group parent')}:</label>
51 </div>
51 </div>
52 <div class="input">
52 <div class="input">
53 ${h.select('group_parent_id','',c.repo_groups,class_="medium")}
53 ${h.select('group_parent_id','',c.repo_groups,class_="medium")}
54 </div>
54 </div>
55 </div>
55 </div>
56
56
57 <div class="buttons">
57 <div class="buttons">
58 ${h.submit('save',_('save'),class_="ui-button")}
58 ${h.submit('save',_('save'),class_="ui-button")}
59 </div>
59 </div>
60 </div>
60 </div>
61 </div>
61 </div>
62 ${h.end_form()}
62 ${h.end_form()}
63 </div>
63 </div>
64 </%def>
64 </%def>
@@ -1,68 +1,68
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.html"/>
2 <%inherit file="/base/base.html"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('Repositories groups administration')} - ${c.rhodecode_name}
5 ${_('Repositories groups administration')} - ${c.rhodecode_name}
6 </%def>
6 </%def>
7
7
8
8
9 <%def name="breadcrumbs_links()">
9 <%def name="breadcrumbs_links()">
10 ${h.link_to(_('Admin'),h.url('admin_home'))} &raquo; ${_('Repositories')}
10 ${h.link_to(_('Admin'),h.url('admin_home'))} &raquo; ${_('Repositories')}
11 </%def>
11 </%def>
12 <%def name="page_nav()">
12 <%def name="page_nav()">
13 ${self.menu('admin')}
13 ${self.menu('admin')}
14 </%def>
14 </%def>
15 <%def name="main()">
15 <%def name="main()">
16 <div class="box">
16 <div class="box">
17 <!-- box / title -->
17 <!-- box / title -->
18 <div class="title">
18 <div class="title">
19 ${self.breadcrumbs()}
19 ${self.breadcrumbs()}
20 <ul class="links">
20 <ul class="links">
21 <li>
21 <li>
22 <span>${h.link_to(_(u'ADD NEW GROUP'),h.url('new_repos_group'))}</span>
22 <span>${h.link_to(_(u'ADD NEW GROUP'),h.url('new_repos_group'))}</span>
23 </li>
23 </li>
24 </ul>
24 </ul>
25 </div>
25 </div>
26 <!-- end box / title -->
26 <!-- end box / title -->
27 <div class="table">
27 <div class="table">
28 % if c.groups:
28 % if c.groups:
29 <table class="table_disp">
29 <table class="table_disp">
30
30
31 <thead>
31 <thead>
32 <tr>
32 <tr>
33 <th class="left"><a href="#">${_('Group name')}</a></th>
33 <th class="left"><a href="#">${_('Group name')}</a></th>
34 <th class="left"><a href="#">${_('Description')}</a></th>
34 <th class="left"><a href="#">${_('Description')}</a></th>
35 <th class="left"><a href="#">${_('Number of repositories')}</a></th>
35 <th class="left"><a href="#">${_('Number of repositories')}</a></th>
36 <th class="left">${_('action')}</th>
36 <th class="left">${_('action')}</th>
37 </tr>
37 </tr>
38 </thead>
38 </thead>
39
39
40 ## REPO GROUPS
40 ## REPO GROUPS
41
41
42 % for gr in c.groups:
42 % for gr in c.groups:
43 <tr>
43 <tr>
44 <td>
44 <td>
45 <div style="white-space: nowrap">
45 <div style="white-space: nowrap">
46 <img class="icon" alt="${_('Repositories group')}" src="${h.url('/images/icons/database_link.png')}"/>
46 <img class="icon" alt="${_('Repositories group')}" src="${h.url('/images/icons/database_link.png')}"/>
47 ${h.link_to(h.literal(' &raquo; '.join([g.group_name for g in gr.parents+[gr]])),url('edit_repos_group',id=gr.group_id))}
47 ${h.link_to(h.literal(' &raquo; '.join([g.name for g in gr.parents+[gr]])),url('edit_repos_group',id=gr.group_id))}
48 </div>
48 </div>
49 </td>
49 </td>
50 <td>${gr.group_description}</td>
50 <td>${gr.group_description}</td>
51 <td><b>${gr.repositories.count()}</b></td>
51 <td><b>${gr.repositories.count()}</b></td>
52 <td>
52 <td>
53 ${h.form(url('repos_group', id=gr.group_id),method='delete')}
53 ${h.form(url('repos_group', id=gr.group_id),method='delete')}
54 ${h.submit('remove_%s' % gr.group_name,'delete',class_="delete_icon action_button",onclick="return confirm('"+_('Confirm to delete this group')+"');")}
54 ${h.submit('remove_%s' % gr.name,'delete',class_="delete_icon action_button",onclick="return confirm('"+_('Confirm to delete this group')+"');")}
55 ${h.end_form()}
55 ${h.end_form()}
56 </td>
56 </td>
57 </tr>
57 </tr>
58 % endfor
58 % endfor
59
59
60 </table>
60 </table>
61 % else:
61 % else:
62 ${_('There are no repositories groups yet')}
62 ${_('There are no repositories groups yet')}
63 % endif
63 % endif
64
64
65 </div>
65 </div>
66 </div>
66 </div>
67
67
68 </%def>
68 </%def>
@@ -1,223 +1,223
1 <%def name="file_class(node)">
1 <%def name="file_class(node)">
2 %if node.is_file():
2 %if node.is_file():
3 <%return "browser-file" %>
3 <%return "browser-file" %>
4 %else:
4 %else:
5 <%return "browser-dir"%>
5 <%return "browser-dir"%>
6 %endif
6 %endif
7 </%def>
7 </%def>
8 <div id="body" class="browserblock">
8 <div id="body" class="browserblock">
9 <div class="browser-header">
9 <div class="browser-header">
10 <div class="browser-nav">
10 <div class="browser-nav">
11 ${h.form(h.url.current())}
11 ${h.form(h.url.current())}
12 <div class="info_box">
12 <div class="info_box">
13 <span class="rev">${_('view')}@rev</span>
13 <span class="rev">${_('view')}@rev</span>
14 <a class="rev" href="${c.url_prev}" title="${_('previous revision')}">&laquo;</a>
14 <a class="ui-button-small" href="${c.url_prev}" title="${_('previous revision')}">&laquo;</a>
15 ${h.text('at_rev',value=c.changeset.revision,size=5)}
15 ${h.text('at_rev',value=c.changeset.revision,size=5)}
16 <a class="rev" href="${c.url_next}" title="${_('next revision')}">&raquo;</a>
16 <a class="ui-button-small" href="${c.url_next}" title="${_('next revision')}">&raquo;</a>
17 ## ${h.submit('view',_('view'),class_="ui-button-small")}
17 ## ${h.submit('view',_('view'),class_="ui-button-small")}
18 </div>
18 </div>
19 ${h.end_form()}
19 ${h.end_form()}
20 </div>
20 </div>
21 <div class="browser-branch">
21 <div class="browser-branch">
22 ${h.checkbox('stay_at_branch',c.changeset.branch,c.changeset.branch==c.branch)}
22 ${h.checkbox('stay_at_branch',c.changeset.branch,c.changeset.branch==c.branch)}
23 <label>${_('follow current branch')}</label>
23 <label>${_('follow current branch')}</label>
24 </div>
24 </div>
25 <div class="browser-search">
25 <div class="browser-search">
26 <div id="search_activate_id" class="search_activate">
26 <div id="search_activate_id" class="search_activate">
27 <a id="filter_activate" href="#">${_('search file list')}</a>
27 <a class="ui-button-small" id="filter_activate" href="#">${_('search file list')}</a>
28 </div>
28 </div>
29 % if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
29 % if h.HasRepoPermissionAny('repository.write','repository.admin')(c.repo_name):
30 <div id="add_node_id" class="add_node">
30 <div id="add_node_id" class="add_node">
31 <a href="${h.url('files_add_home',repo_name=c.repo_name,revision=c.changeset.raw_id,f_path=c.f_path)}">${_('add new file')}</a>
31 <a class="ui-button-small" href="${h.url('files_add_home',repo_name=c.repo_name,revision=c.changeset.raw_id,f_path=c.f_path)}">${_('add new file')}</a>
32 </div>
32 </div>
33 % endif
33 % endif
34 <div>
34 <div>
35 <div id="node_filter_box_loading" style="display:none">${_('Loading file list...')}</div>
35 <div id="node_filter_box_loading" style="display:none">${_('Loading file list...')}</div>
36 <div id="node_filter_box" style="display:none">
36 <div id="node_filter_box" style="display:none">
37 ${h.files_breadcrumbs(c.repo_name,c.changeset.raw_id,c.files_list.path)}/<input type="text" value="type to search..." name="filter" size="25" id="node_filter" autocomplete="off">
37 ${h.files_breadcrumbs(c.repo_name,c.changeset.raw_id,c.files_list.path)}/<input type="text" value="type to search..." name="filter" size="25" id="node_filter" autocomplete="off">
38
38
39 <script type="text/javascript">
39 <script type="text/javascript">
40
40
41 YUE.on('stay_at_branch','click',function(e){
41 YUE.on('stay_at_branch','click',function(e){
42 if(e.target.checked){
42 if(e.target.checked){
43 var uri = "${h.url.current(branch='__BRANCH__')}"
43 var uri = "${h.url.current(branch='__BRANCH__')}"
44 uri = uri.replace('__BRANCH__',e.target.value);
44 uri = uri.replace('__BRANCH__',e.target.value);
45 window.location = uri;
45 window.location = uri;
46 }
46 }
47 else{
47 else{
48 window.location = "${h.url.current()}";
48 window.location = "${h.url.current()}";
49 }
49 }
50
50
51 })
51 })
52
52
53 var n_filter = YUD.get('node_filter');
53 var n_filter = YUD.get('node_filter');
54 var F = YAHOO.namespace('node_filter');
54 var F = YAHOO.namespace('node_filter');
55
55
56 var url = '${h.url("files_nodelist_home",repo_name="__REPO__",revision="__REVISION__",f_path="__FPATH__")}';
56 var url = '${h.url("files_nodelist_home",repo_name="__REPO__",revision="__REVISION__",f_path="__FPATH__")}';
57 var node_url = '${h.url("files_home",repo_name="__REPO__",revision="__REVISION__",f_path="__FPATH__")}';
57 var node_url = '${h.url("files_home",repo_name="__REPO__",revision="__REVISION__",f_path="__FPATH__")}';
58
58
59 url = url.replace('__REPO__','${c.repo_name}');
59 url = url.replace('__REPO__','${c.repo_name}');
60 url = url.replace('__REVISION__','${c.changeset.raw_id}');
60 url = url.replace('__REVISION__','${c.changeset.raw_id}');
61 url = url.replace('__FPATH__','${c.files_list.path}');
61 url = url.replace('__FPATH__','${c.files_list.path}');
62
62
63 node_url = node_url.replace('__REPO__','${c.repo_name}');
63 node_url = node_url.replace('__REPO__','${c.repo_name}');
64 node_url = node_url.replace('__REVISION__','${c.changeset.raw_id}');
64 node_url = node_url.replace('__REVISION__','${c.changeset.raw_id}');
65
65
66
66
67 F.filterTimeout = null;
67 F.filterTimeout = null;
68 var nodes = null;
68 var nodes = null;
69
69
70
70
71 F.initFilter = function(){
71 F.initFilter = function(){
72 YUD.setStyle('node_filter_box_loading','display','');
72 YUD.setStyle('node_filter_box_loading','display','');
73 YUD.setStyle('search_activate_id','display','none');
73 YUD.setStyle('search_activate_id','display','none');
74 YUD.setStyle('add_node_id','display','none');
74 YUD.setStyle('add_node_id','display','none');
75 YUC.initHeader('X-PARTIAL-XHR',true);
75 YUC.initHeader('X-PARTIAL-XHR',true);
76 YUC.asyncRequest('GET',url,{
76 YUC.asyncRequest('GET',url,{
77 success:function(o){
77 success:function(o){
78 nodes = JSON.parse(o.responseText);
78 nodes = JSON.parse(o.responseText);
79 YUD.setStyle('node_filter_box_loading','display','none');
79 YUD.setStyle('node_filter_box_loading','display','none');
80 YUD.setStyle('node_filter_box','display','');
80 YUD.setStyle('node_filter_box','display','');
81 },
81 },
82 failure:function(o){
82 failure:function(o){
83 console.log('failed to load');
83 console.log('failed to load');
84 }
84 }
85 },null);
85 },null);
86 }
86 }
87
87
88 F.updateFilter = function(e) {
88 F.updateFilter = function(e) {
89
89
90 return function(){
90 return function(){
91 // Reset timeout
91 // Reset timeout
92 F.filterTimeout = null;
92 F.filterTimeout = null;
93 var query = e.target.value;
93 var query = e.target.value;
94 var match = [];
94 var match = [];
95 var matches = 0;
95 var matches = 0;
96 var matches_max = 20;
96 var matches_max = 20;
97 if (query != ""){
97 if (query != ""){
98 for(var i=0;i<nodes.length;i++){
98 for(var i=0;i<nodes.length;i++){
99 var pos = nodes[i].toLowerCase().indexOf(query)
99 var pos = nodes[i].toLowerCase().indexOf(query)
100 if(query && pos != -1){
100 if(query && pos != -1){
101
101
102 matches++
102 matches++
103 //show only certain amount to not kill browser
103 //show only certain amount to not kill browser
104 if (matches > matches_max){
104 if (matches > matches_max){
105 break;
105 break;
106 }
106 }
107
107
108 var n = nodes[i];
108 var n = nodes[i];
109 var n_hl = n.substring(0,pos)
109 var n_hl = n.substring(0,pos)
110 +"<b>{0}</b>".format(n.substring(pos,pos+query.length))
110 +"<b>{0}</b>".format(n.substring(pos,pos+query.length))
111 +n.substring(pos+query.length)
111 +n.substring(pos+query.length)
112 match.push('<tr><td><a class="browser-file" href="{0}">{1}</a></td><td colspan="5"></td></tr>'.format(node_url.replace('__FPATH__',n),n_hl));
112 match.push('<tr><td><a class="browser-file" href="{0}">{1}</a></td><td colspan="5"></td></tr>'.format(node_url.replace('__FPATH__',n),n_hl));
113 }
113 }
114 if(match.length >= matches_max){
114 if(match.length >= matches_max){
115 match.push('<tr><td>{0}</td><td colspan="5"></td></tr>'.format("${_('search truncated')}"));
115 match.push('<tr><td>{0}</td><td colspan="5"></td></tr>'.format("${_('search truncated')}"));
116 }
116 }
117
117
118 }
118 }
119 }
119 }
120
120
121 if(query != ""){
121 if(query != ""){
122 YUD.setStyle('tbody','display','none');
122 YUD.setStyle('tbody','display','none');
123 YUD.setStyle('tbody_filtered','display','');
123 YUD.setStyle('tbody_filtered','display','');
124
124
125 if (match.length==0){
125 if (match.length==0){
126 match.push('<tr><td>{0}</td><td colspan="5"></td></tr>'.format("${_('no matching files')}"));
126 match.push('<tr><td>{0}</td><td colspan="5"></td></tr>'.format("${_('no matching files')}"));
127 }
127 }
128
128
129 YUD.get('tbody_filtered').innerHTML = match.join("");
129 YUD.get('tbody_filtered').innerHTML = match.join("");
130 }
130 }
131 else{
131 else{
132 YUD.setStyle('tbody','display','');
132 YUD.setStyle('tbody','display','');
133 YUD.setStyle('tbody_filtered','display','none');
133 YUD.setStyle('tbody_filtered','display','none');
134 }
134 }
135
135
136 }
136 }
137 }
137 }
138
138
139
139
140 YUE.on(YUD.get('filter_activate'),'click',function(){
140 YUE.on(YUD.get('filter_activate'),'click',function(){
141 F.initFilter();
141 F.initFilter();
142 })
142 })
143 YUE.on(n_filter,'click',function(){
143 YUE.on(n_filter,'click',function(){
144 n_filter.value = '';
144 n_filter.value = '';
145 });
145 });
146 YUE.on(n_filter,'keyup',function(e){
146 YUE.on(n_filter,'keyup',function(e){
147 clearTimeout(F.filterTimeout);
147 clearTimeout(F.filterTimeout);
148 F.filterTimeout = setTimeout(F.updateFilter(e),600);
148 F.filterTimeout = setTimeout(F.updateFilter(e),600);
149 });
149 });
150 </script>
150 </script>
151
151
152 </div>
152 </div>
153 </div>
153 </div>
154 </div>
154 </div>
155 </div>
155 </div>
156
156
157 <div class="browser-body">
157 <div class="browser-body">
158 <table class="code-browser">
158 <table class="code-browser">
159 <thead>
159 <thead>
160 <tr>
160 <tr>
161 <th>${_('Name')}</th>
161 <th>${_('Name')}</th>
162 <th>${_('Size')}</th>
162 <th>${_('Size')}</th>
163 <th>${_('Mimetype')}</th>
163 <th>${_('Mimetype')}</th>
164 <th>${_('Revision')}</th>
164 <th>${_('Revision')}</th>
165 <th>${_('Last modified')}</th>
165 <th>${_('Last modified')}</th>
166 <th>${_('Last commiter')}</th>
166 <th>${_('Last commiter')}</th>
167 </tr>
167 </tr>
168 </thead>
168 </thead>
169
169
170 <tbody id="tbody">
170 <tbody id="tbody">
171 %if c.files_list.parent:
171 %if c.files_list.parent:
172 <tr class="parity0">
172 <tr class="parity0">
173 <td>
173 <td>
174 ${h.link_to('..',h.url('files_home',repo_name=c.repo_name,revision=c.changeset.raw_id,f_path=c.files_list.parent.path),class_="browser-dir")}
174 ${h.link_to('..',h.url('files_home',repo_name=c.repo_name,revision=c.changeset.raw_id,f_path=c.files_list.parent.path),class_="browser-dir")}
175 </td>
175 </td>
176 <td></td>
176 <td></td>
177 <td></td>
177 <td></td>
178 <td></td>
178 <td></td>
179 <td></td>
179 <td></td>
180 <td></td>
180 <td></td>
181 </tr>
181 </tr>
182 %endif
182 %endif
183
183
184 %for cnt,node in enumerate(c.files_list):
184 %for cnt,node in enumerate(c.files_list):
185 <tr class="parity${cnt%2}">
185 <tr class="parity${cnt%2}">
186 <td>
186 <td>
187 ${h.link_to(node.name,h.url('files_home',repo_name=c.repo_name,revision=c.changeset.raw_id,f_path=h.safe_unicode(node.path)),class_=file_class(node))}
187 ${h.link_to(node.name,h.url('files_home',repo_name=c.repo_name,revision=c.changeset.raw_id,f_path=h.safe_unicode(node.path)),class_=file_class(node))}
188 </td>
188 </td>
189 <td>
189 <td>
190 %if node.is_file():
190 %if node.is_file():
191 ${h.format_byte_size(node.size,binary=True)}
191 ${h.format_byte_size(node.size,binary=True)}
192 %endif
192 %endif
193 </td>
193 </td>
194 <td>
194 <td>
195 %if node.is_file():
195 %if node.is_file():
196 ${node.mimetype}
196 ${node.mimetype}
197 %endif
197 %endif
198 </td>
198 </td>
199 <td>
199 <td>
200 %if node.is_file():
200 %if node.is_file():
201 <span class="tooltip" title="${node.last_changeset.raw_id}">
201 <span class="tooltip" title="${node.last_changeset.raw_id}">
202 ${'r%s:%s' % (node.last_changeset.revision,node.last_changeset.short_id)}</span>
202 ${'r%s:%s' % (node.last_changeset.revision,node.last_changeset.short_id)}</span>
203 %endif
203 %endif
204 </td>
204 </td>
205 <td>
205 <td>
206 %if node.is_file():
206 %if node.is_file():
207 <span class="tooltip" title="${node.last_changeset.date}">
207 <span class="tooltip" title="${node.last_changeset.date}">
208 ${h.age(node.last_changeset.date)}</span>
208 ${h.age(node.last_changeset.date)}</span>
209 %endif
209 %endif
210 </td>
210 </td>
211 <td>
211 <td>
212 %if node.is_file():
212 %if node.is_file():
213 ${node.last_changeset.author}
213 ${node.last_changeset.author}
214 %endif
214 %endif
215 </td>
215 </td>
216 </tr>
216 </tr>
217 %endfor
217 %endfor
218 </tbody>
218 </tbody>
219 <tbody id="tbody_filtered" style="display:none">
219 <tbody id="tbody_filtered" style="display:none">
220 </tbody>
220 </tbody>
221 </table>
221 </table>
222 </div>
222 </div>
223 </div> No newline at end of file
223 </div>
@@ -1,226 +1,226
1 <%page args="parent" />
1 <%page args="parent" />
2 <div class="box">
2 <div class="box">
3 <!-- box / title -->
3 <!-- box / title -->
4 <div class="title">
4 <div class="title">
5 <h5>
5 <h5>
6 <input class="top-right-rounded-corner top-left-rounded-corner bottom-left-rounded-corner bottom-right-rounded-corner" id="q_filter" size="15" type="text" name="filter" value="${_('quick filter...')}"/>
6 <input class="top-right-rounded-corner top-left-rounded-corner bottom-left-rounded-corner bottom-right-rounded-corner" id="q_filter" size="15" type="text" name="filter" value="${_('quick filter...')}"/>
7 ${parent.breadcrumbs()} <span id="repo_count"></span> ${_('repositories')}
7 ${parent.breadcrumbs()} <span id="repo_count"></span> ${_('repositories')}
8 </h5>
8 </h5>
9 %if c.rhodecode_user.username != 'default':
9 %if c.rhodecode_user.username != 'default':
10 %if h.HasPermissionAny('hg.admin','hg.create.repository')():
10 %if h.HasPermissionAny('hg.admin','hg.create.repository')():
11 <ul class="links">
11 <ul class="links">
12 <li>
12 <li>
13 <span>${h.link_to(_('ADD NEW REPOSITORY'),h.url('admin_settings_create_repository'))}</span>
13 <span>${h.link_to(_('ADD NEW REPOSITORY'),h.url('admin_settings_create_repository'))}</span>
14 </li>
14 </li>
15 </ul>
15 </ul>
16 %endif
16 %endif
17 %endif
17 %endif
18 </div>
18 </div>
19 <!-- end box / title -->
19 <!-- end box / title -->
20 <div class="table">
20 <div class="table">
21 % if c.groups:
21 % if c.groups:
22 <table>
22 <table>
23 <thead>
23 <thead>
24 <tr>
24 <tr>
25 <th class="left"><a href="#">${_('Group name')}</a></th>
25 <th class="left"><a href="#">${_('Group name')}</a></th>
26 <th class="left"><a href="#">${_('Description')}</a></th>
26 <th class="left"><a href="#">${_('Description')}</a></th>
27 ##<th class="left"><a href="#">${_('Number of repositories')}</a></th>
27 ##<th class="left"><a href="#">${_('Number of repositories')}</a></th>
28 </tr>
28 </tr>
29 </thead>
29 </thead>
30
30
31 ## REPO GROUPS
31 ## REPO GROUPS
32
32
33 % for gr in c.groups:
33 % for gr in c.groups:
34 <tr>
34 <tr>
35 <td>
35 <td>
36 <div style="white-space: nowrap">
36 <div style="white-space: nowrap">
37 <img class="icon" alt="${_('Repositories group')}" src="${h.url('/images/icons/database_link.png')}"/>
37 <img class="icon" alt="${_('Repositories group')}" src="${h.url('/images/icons/database_link.png')}"/>
38 ${h.link_to(gr.group_name,url('repos_group',id=gr.group_id))}
38 ${h.link_to(gr.name,url('repos_group_home',group_name=gr.group_name))}
39 </div>
39 </div>
40 </td>
40 </td>
41 <td>${gr.group_description}</td>
41 <td>${gr.group_description}</td>
42 ##<td><b>${gr.repositories.count()}</b></td>
42 ##<td><b>${gr.repositories.count()}</b></td>
43 </tr>
43 </tr>
44 % endfor
44 % endfor
45
45
46 </table>
46 </table>
47 <div style="height: 20px"></div>
47 <div style="height: 20px"></div>
48 % endif
48 % endif
49 <div id="welcome" style="display:none;text-align:center">
49 <div id="welcome" style="display:none;text-align:center">
50 <h1><a href="${h.url('home')}">${c.rhodecode_name} ${c.rhodecode_version}</a></h1>
50 <h1><a href="${h.url('home')}">${c.rhodecode_name} ${c.rhodecode_version}</a></h1>
51 </div>
51 </div>
52 <table id="repos_list">
52 <table id="repos_list">
53 <thead>
53 <thead>
54 <tr>
54 <tr>
55 <th class="left"></th>
55 <th class="left"></th>
56 <th class="left">${_('Name')}</th>
56 <th class="left">${_('Name')}</th>
57 <th class="left">${_('Description')}</th>
57 <th class="left">${_('Description')}</th>
58 <th class="left">${_('Last change')}</th>
58 <th class="left">${_('Last change')}</th>
59 <th class="left">${_('Tip')}</th>
59 <th class="left">${_('Tip')}</th>
60 <th class="left">${_('Owner')}</th>
60 <th class="left">${_('Owner')}</th>
61 <th class="left">${_('RSS')}</th>
61 <th class="left">${_('RSS')}</th>
62 <th class="left">${_('Atom')}</th>
62 <th class="left">${_('Atom')}</th>
63 </tr>
63 </tr>
64 </thead>
64 </thead>
65 <tbody>
65 <tbody>
66 %for cnt,repo in enumerate(c.repos_list):
66 %for cnt,repo in enumerate(c.repos_list):
67 <tr class="parity${cnt%2}">
67 <tr class="parity${cnt%2}">
68 <td class="quick_repo_menu">
68 <td class="quick_repo_menu">
69 <ul class="menu_items hidden">
69 <ul class="menu_items hidden">
70 <li>
70 <li>
71 <a title="${_('Summary')}" href="${h.url('summary_home',repo_name=repo['name'])}">
71 <a title="${_('Summary')}" href="${h.url('summary_home',repo_name=repo['name'])}">
72 <span class="icon">
72 <span class="icon">
73 <img src="${h.url('/images/icons/clipboard_16.png')}" alt="${_('Summary')}" />
73 <img src="${h.url('/images/icons/clipboard_16.png')}" alt="${_('Summary')}" />
74 </span>
74 </span>
75 <span>${_('Summary')}</span>
75 <span>${_('Summary')}</span>
76 </a>
76 </a>
77 </li>
77 </li>
78 <li>
78 <li>
79 <a title="${_('Changelog')}" href="${h.url('changelog_home',repo_name=repo['name'])}">
79 <a title="${_('Changelog')}" href="${h.url('changelog_home',repo_name=repo['name'])}">
80 <span class="icon">
80 <span class="icon">
81 <img src="${h.url('/images/icons/time.png')}" alt="${_('Changelog')}" />
81 <img src="${h.url('/images/icons/time.png')}" alt="${_('Changelog')}" />
82 </span>
82 </span>
83 <span>${_('Changelog')}</span>
83 <span>${_('Changelog')}</span>
84 </a>
84 </a>
85 </li>
85 </li>
86 <li>
86 <li>
87 <a title="${_('Files')}" href="${h.url('files_home',repo_name=repo['name'])}">
87 <a title="${_('Files')}" href="${h.url('files_home',repo_name=repo['name'])}">
88 <span class="icon">
88 <span class="icon">
89 <img src="${h.url('/images/icons/file.png')}" alt="${_('Files')}" />
89 <img src="${h.url('/images/icons/file.png')}" alt="${_('Files')}" />
90 </span>
90 </span>
91 <span>${_('Files')}</span>
91 <span>${_('Files')}</span>
92 </a>
92 </a>
93 </li>
93 </li>
94 </ul>
94 </ul>
95 </td>
95 </td>
96 <td>
96 <td>
97 ## TYPE OF REPO
97 ## TYPE OF REPO
98 <div style="white-space: nowrap">
98 <div style="white-space: nowrap">
99 %if repo['dbrepo']['repo_type'] =='hg':
99 %if repo['dbrepo']['repo_type'] =='hg':
100 <img class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="${h.url('/images/icons/hgicon.png')}"/>
100 <img class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="${h.url('/images/icons/hgicon.png')}"/>
101 %elif repo['dbrepo']['repo_type'] =='git':
101 %elif repo['dbrepo']['repo_type'] =='git':
102 <img class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="${h.url('/images/icons/giticon.png')}"/>
102 <img class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="${h.url('/images/icons/giticon.png')}"/>
103 %endif
103 %endif
104
104
105 ##PRIVATE/PUBLIC
105 ##PRIVATE/PUBLIC
106 %if repo['dbrepo']['private']:
106 %if repo['dbrepo']['private']:
107 <img class="icon" title="${_('private repository')}" alt="${_('private repository')}" src="${h.url('/images/icons/lock.png')}"/>
107 <img class="icon" title="${_('private repository')}" alt="${_('private repository')}" src="${h.url('/images/icons/lock.png')}"/>
108 %else:
108 %else:
109 <img class="icon" title="${_('public repository')}" alt="${_('public repository')}" src="${h.url('/images/icons/lock_open.png')}"/>
109 <img class="icon" title="${_('public repository')}" alt="${_('public repository')}" src="${h.url('/images/icons/lock_open.png')}"/>
110 %endif
110 %endif
111
111
112 ##NAME
112 ##NAME
113 ${h.link_to(repo['name'],
113 ${h.link_to(repo['name'],
114 h.url('summary_home',repo_name=repo['name']),class_="repo_name")}
114 h.url('summary_home',repo_name=repo['name']),class_="repo_name")}
115 %if repo['dbrepo_fork']:
115 %if repo['dbrepo_fork']:
116 <a href="${h.url('summary_home',repo_name=repo['dbrepo_fork']['repo_name'])}">
116 <a href="${h.url('summary_home',repo_name=repo['dbrepo_fork']['repo_name'])}">
117 <img class="icon" alt="${_('fork')}"
117 <img class="icon" alt="${_('fork')}"
118 title="${_('Fork of')} ${repo['dbrepo_fork']['repo_name']}"
118 title="${_('Fork of')} ${repo['dbrepo_fork']['repo_name']}"
119 src="${h.url('/images/icons/arrow_divide.png')}"/></a>
119 src="${h.url('/images/icons/arrow_divide.png')}"/></a>
120 %endif
120 %endif
121 </div>
121 </div>
122 </td>
122 </td>
123 ##DESCRIPTION
123 ##DESCRIPTION
124 <td><span class="tooltip" title="${h.tooltip(repo['description'])}">
124 <td><span class="tooltip" title="${h.tooltip(repo['description'])}">
125 ${h.truncate(repo['description'],60)}</span>
125 ${h.truncate(repo['description'],60)}</span>
126 </td>
126 </td>
127 ##LAST CHANGE
127 ##LAST CHANGE
128 <td>
128 <td>
129 <span class="tooltip" title="${repo['last_change']}">
129 <span class="tooltip" title="${repo['last_change']}">
130 ${h.age(repo['last_change'])}</span>
130 ${h.age(repo['last_change'])}</span>
131 </td>
131 </td>
132 <td>
132 <td>
133 %if repo['rev']>=0:
133 %if repo['rev']>=0:
134 <a title="${h.tooltip('%s\n%s' % (repo['author'],repo['last_msg']))}" class="tooltip" href="${h.url('changeset_home',repo_name=repo['name'],revision=repo['tip'])}">${'r%s:%s' % (repo['rev'],h.short_id(repo['tip']))}</a>
134 <a title="${h.tooltip('%s\n%s' % (repo['author'],repo['last_msg']))}" class="tooltip" href="${h.url('changeset_home',repo_name=repo['name'],revision=repo['tip'])}">${'r%s:%s' % (repo['rev'],h.short_id(repo['tip']))}</a>
135 %else:
135 %else:
136 ${_('No changesets yet')}
136 ${_('No changesets yet')}
137 %endif
137 %endif
138 </td>
138 </td>
139 <td title="${repo['contact']}">${h.person(repo['contact'])}</td>
139 <td title="${repo['contact']}">${h.person(repo['contact'])}</td>
140 <td>
140 <td>
141 %if c.rhodecode_user.username != 'default':
141 %if c.rhodecode_user.username != 'default':
142 <a title="${_('Subscribe to %s rss feed')%repo['name']}" class="rss_icon" href="${h.url('rss_feed_home',repo_name=repo['name'],api_key=c.rhodecode_user.api_key)}"></a>
142 <a title="${_('Subscribe to %s rss feed')%repo['name']}" class="rss_icon" href="${h.url('rss_feed_home',repo_name=repo['name'],api_key=c.rhodecode_user.api_key)}"></a>
143 %else:
143 %else:
144 <a title="${_('Subscribe to %s rss feed')%repo['name']}" class="rss_icon" href="${h.url('rss_feed_home',repo_name=repo['name'])}"></a>
144 <a title="${_('Subscribe to %s rss feed')%repo['name']}" class="rss_icon" href="${h.url('rss_feed_home',repo_name=repo['name'])}"></a>
145 %endif:
145 %endif:
146 </td>
146 </td>
147 <td>
147 <td>
148 %if c.rhodecode_user.username != 'default':
148 %if c.rhodecode_user.username != 'default':
149 <a title="${_('Subscribe to %s atom feed')%repo['name']}" class="atom_icon" href="${h.url('atom_feed_home',repo_name=repo['name'],api_key=c.rhodecode_user.api_key)}"></a>
149 <a title="${_('Subscribe to %s atom feed')%repo['name']}" class="atom_icon" href="${h.url('atom_feed_home',repo_name=repo['name'],api_key=c.rhodecode_user.api_key)}"></a>
150 %else:
150 %else:
151 <a title="${_('Subscribe to %s atom feed')%repo['name']}" class="atom_icon" href="${h.url('atom_feed_home',repo_name=repo['name'])}"></a>
151 <a title="${_('Subscribe to %s atom feed')%repo['name']}" class="atom_icon" href="${h.url('atom_feed_home',repo_name=repo['name'])}"></a>
152 %endif:
152 %endif:
153 </td>
153 </td>
154 </tr>
154 </tr>
155 %endfor
155 %endfor
156 </tbody>
156 </tbody>
157 </table>
157 </table>
158 </div>
158 </div>
159 </div>
159 </div>
160
160
161
161
162 <script type="text/javascript">
162 <script type="text/javascript">
163 var D = YAHOO.util.Dom;
163 var D = YAHOO.util.Dom;
164 var E = YAHOO.util.Event;
164 var E = YAHOO.util.Event;
165 var S = YAHOO.util.Selector;
165 var S = YAHOO.util.Selector;
166
166
167 var q_filter = D.get('q_filter');
167 var q_filter = D.get('q_filter');
168 var F = YAHOO.namespace('q_filter');
168 var F = YAHOO.namespace('q_filter');
169
169
170 E.on(q_filter,'click',function(){
170 E.on(q_filter,'click',function(){
171 q_filter.value = '';
171 q_filter.value = '';
172 });
172 });
173
173
174 F.filterTimeout = null;
174 F.filterTimeout = null;
175
175
176 function set_count(count){
176 function set_count(count){
177
177
178 if(count == 0){
178 if(count == 0){
179 YUD.setStyle('repos_list','display','none');
179 YUD.setStyle('repos_list','display','none');
180 YUD.setStyle('welcome','display','');
180 YUD.setStyle('welcome','display','');
181 }
181 }
182 else{
182 else{
183 YUD.setStyle('repos_list','display','');
183 YUD.setStyle('repos_list','display','');
184 YUD.setStyle('welcome','display','none');
184 YUD.setStyle('welcome','display','none');
185 }
185 }
186 YUD.get('repo_count').innerHTML = count;
186 YUD.get('repo_count').innerHTML = count;
187
187
188 }
188 }
189
189
190
190
191 //set initial count for repos
191 //set initial count for repos
192 var nodes = S.query('div.table tr td div a.repo_name');
192 var nodes = S.query('div.table tr td div a.repo_name');
193
193
194 set_count(nodes.length)
194 set_count(nodes.length)
195 F.updateFilter = function() {
195 F.updateFilter = function() {
196 // Reset timeout
196 // Reset timeout
197 F.filterTimeout = null;
197 F.filterTimeout = null;
198
198
199 var obsolete = [];
199 var obsolete = [];
200 nodes = S.query('div.table tr td div a.repo_name');
200 nodes = S.query('div.table tr td div a.repo_name');
201 var req = q_filter.value.toLowerCase();
201 var req = q_filter.value.toLowerCase();
202 for (n in nodes){
202 for (n in nodes){
203 D.setStyle(nodes[n].parentNode.parentNode.parentNode,'display','')
203 D.setStyle(nodes[n].parentNode.parentNode.parentNode,'display','')
204 }
204 }
205 if (req){
205 if (req){
206 for (n in nodes){
206 for (n in nodes){
207 if (nodes[n].innerHTML.toLowerCase().indexOf(req) == -1) {
207 if (nodes[n].innerHTML.toLowerCase().indexOf(req) == -1) {
208 obsolete.push(nodes[n]);
208 obsolete.push(nodes[n]);
209 }
209 }
210 }
210 }
211 if(obsolete){
211 if(obsolete){
212 for (n in obsolete){
212 for (n in obsolete){
213 D.setStyle(obsolete[n].parentNode.parentNode.parentNode,'display','none');
213 D.setStyle(obsolete[n].parentNode.parentNode.parentNode,'display','none');
214 }
214 }
215 }
215 }
216 }
216 }
217 // set new count into dashboard
217 // set new count into dashboard
218 set_count(nodes.length - obsolete.length)
218 set_count(nodes.length - obsolete.length)
219 }
219 }
220
220
221 E.on(q_filter,'keyup',function(e){
221 E.on(q_filter,'keyup',function(e){
222 clearTimeout(F.filterTimeout);
222 clearTimeout(F.filterTimeout);
223 F.filterTimeout = setTimeout(F.updateFilter,600);
223 F.filterTimeout = setTimeout(F.updateFilter,600);
224 });
224 });
225
225
226 </script>
226 </script>
@@ -1,700 +1,700
1 <%inherit file="/base/base.html"/>
1 <%inherit file="/base/base.html"/>
2
2
3 <%def name="title()">
3 <%def name="title()">
4 ${c.repo_name} ${_('Summary')} - ${c.rhodecode_name}
4 ${c.repo_name} ${_('Summary')} - ${c.rhodecode_name}
5 </%def>
5 </%def>
6
6
7 <%def name="breadcrumbs_links()">
7 <%def name="breadcrumbs_links()">
8 ${h.link_to(u'Home',h.url('/'))}
8 ${h.link_to(u'Home',h.url('/'))}
9 &raquo;
9 &raquo;
10 ${h.link_to(c.dbrepo.just_name,h.url('summary_home',repo_name=c.repo_name))}
10 ${h.link_to(c.dbrepo.just_name,h.url('summary_home',repo_name=c.repo_name))}
11 &raquo;
11 &raquo;
12 ${_('summary')}
12 ${_('summary')}
13 </%def>
13 </%def>
14
14
15 <%def name="page_nav()">
15 <%def name="page_nav()">
16 ${self.menu('summary')}
16 ${self.menu('summary')}
17 </%def>
17 </%def>
18
18
19 <%def name="main()">
19 <%def name="main()">
20 <div class="box box-left">
20 <div class="box box-left">
21 <!-- box / title -->
21 <!-- box / title -->
22 <div class="title">
22 <div class="title">
23 ${self.breadcrumbs()}
23 ${self.breadcrumbs()}
24 </div>
24 </div>
25 <!-- end box / title -->
25 <!-- end box / title -->
26 <div class="form">
26 <div class="form">
27 <div id="summary" class="fields">
27 <div id="summary" class="fields">
28
28
29 <div class="field">
29 <div class="field">
30 <div class="label">
30 <div class="label">
31 <label>${_('Name')}:</label>
31 <label>${_('Name')}:</label>
32 </div>
32 </div>
33 <div class="input-short">
33 <div class="input-short">
34 %if c.rhodecode_user.username != 'default':
34 %if c.rhodecode_user.username != 'default':
35 %if c.following:
35 %if c.following:
36 <span id="follow_toggle" class="following" title="${_('Stop following this repository')}"
36 <span id="follow_toggle" class="following" title="${_('Stop following this repository')}"
37 onclick="javascript:toggleFollowingRepo(this,${c.dbrepo.repo_id},'${str(h.get_token())}')">
37 onclick="javascript:toggleFollowingRepo(this,${c.dbrepo.repo_id},'${str(h.get_token())}')">
38 </span>
38 </span>
39 %else:
39 %else:
40 <span id="follow_toggle" class="follow" title="${_('Start following this repository')}"
40 <span id="follow_toggle" class="follow" title="${_('Start following this repository')}"
41 onclick="javascript:toggleFollowingRepo(this,${c.dbrepo.repo_id},'${str(h.get_token())}')">
41 onclick="javascript:toggleFollowingRepo(this,${c.dbrepo.repo_id},'${str(h.get_token())}')">
42 </span>
42 </span>
43 %endif
43 %endif
44 %endif:
44 %endif:
45
45
46 ##REPO TYPE
46 ##REPO TYPE
47 %if c.dbrepo.repo_type =='hg':
47 %if c.dbrepo.repo_type =='hg':
48 <img style="margin-bottom:2px" class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="${h.url("/images/icons/hgicon.png")}"/>
48 <img style="margin-bottom:2px" class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="${h.url('/images/icons/hgicon.png')}"/>
49 %endif
49 %endif
50 %if c.dbrepo.repo_type =='git':
50 %if c.dbrepo.repo_type =='git':
51 <img style="margin-bottom:2px" class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="${h.url("/images/icons/giticon.png")}"/>
51 <img style="margin-bottom:2px" class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="${h.url('/images/icons/giticon.png')}"/>
52 %endif
52 %endif
53
53
54 ##PUBLIC/PRIVATE
54 ##PUBLIC/PRIVATE
55 %if c.dbrepo.private:
55 %if c.dbrepo.private:
56 <img style="margin-bottom:2px" class="icon" title="${_('private repository')}" alt="${_('private repository')}" src="${h.url("/images/icons/lock.png")}"/>
56 <img style="margin-bottom:2px" class="icon" title="${_('private repository')}" alt="${_('private repository')}" src="${h.url('/images/icons/lock.png')}"/>
57 %else:
57 %else:
58 <img style="margin-bottom:2px" class="icon" title="${_('public repository')}" alt="${_('public repository')}" src="${h.url("/images/icons/lock_open.png")}"/>
58 <img style="margin-bottom:2px" class="icon" title="${_('public repository')}" alt="${_('public repository')}" src="${h.url('/images/icons/lock_open.png')}"/>
59 %endif
59 %endif
60
60
61 ##REPO NAME
61 ##REPO NAME
62 <span class="repo_name">${h.repo_link(c.dbrepo.groups_and_repo)}</span>
62 <span class="repo_name">${h.repo_link(c.dbrepo.groups_and_repo)}</span>
63
63
64 ##FORK
64 ##FORK
65 %if c.dbrepo.fork:
65 %if c.dbrepo.fork:
66 <div style="margin-top:5px;clear:both"">
66 <div style="margin-top:5px;clear:both"">
67 <a href="${h.url('summary_home',repo_name=c.dbrepo.fork.repo_name)}">
67 <a href="${h.url('summary_home',repo_name=c.dbrepo.fork.repo_name)}">
68 <img class="icon" alt="${_('public')}"
68 <img class="icon" alt="${_('public')}"
69 title="${_('Fork of')} ${c.dbrepo.fork.repo_name}"
69 title="${_('Fork of')} ${c.dbrepo.fork.repo_name}"
70 src="${h.url("/images/icons/arrow_divide.png")}"/>
70 src="${h.url('/images/icons/arrow_divide.png')}"/>
71 ${_('Fork of')} ${c.dbrepo.fork.repo_name}
71 ${_('Fork of')} ${c.dbrepo.fork.repo_name}
72 </a>
72 </a>
73 </div>
73 </div>
74 %endif
74 %endif
75 ##REMOTE
75 ##REMOTE
76 %if c.dbrepo.clone_uri:
76 %if c.dbrepo.clone_uri:
77 <div style="margin-top:5px;clear:both">
77 <div style="margin-top:5px;clear:both">
78 <a href="${h.url(str(h.hide_credentials(c.dbrepo.clone_uri)))}">
78 <a href="${h.url(str(h.hide_credentials(c.dbrepo.clone_uri)))}">
79 <img class="icon" alt="${_('remote clone')}"
79 <img class="icon" alt="${_('remote clone')}"
80 title="${_('Clone from')} ${h.hide_credentials(c.dbrepo.clone_uri)}"
80 title="${_('Clone from')} ${h.hide_credentials(c.dbrepo.clone_uri)}"
81 src="${h.url("/images/icons/connect.png")}"/>
81 src="${h.url('/images/icons/connect.png')}"/>
82 ${_('Clone from')} ${h.hide_credentials(c.dbrepo.clone_uri)}
82 ${_('Clone from')} ${h.hide_credentials(c.dbrepo.clone_uri)}
83 </a>
83 </a>
84 </div>
84 </div>
85 %endif
85 %endif
86 </div>
86 </div>
87 </div>
87 </div>
88
88
89
89
90 <div class="field">
90 <div class="field">
91 <div class="label">
91 <div class="label">
92 <label>${_('Description')}:</label>
92 <label>${_('Description')}:</label>
93 </div>
93 </div>
94 <div class="input-short desc">${h.urlify_text(c.dbrepo.description)}</div>
94 <div class="input-short desc">${h.urlify_text(c.dbrepo.description)}</div>
95 </div>
95 </div>
96
96
97
97
98 <div class="field">
98 <div class="field">
99 <div class="label">
99 <div class="label">
100 <label>${_('Contact')}:</label>
100 <label>${_('Contact')}:</label>
101 </div>
101 </div>
102 <div class="input-short">
102 <div class="input-short">
103 <div class="gravatar">
103 <div class="gravatar">
104 <img alt="gravatar" src="${h.gravatar_url(c.dbrepo.user.email)}"/>
104 <img alt="gravatar" src="${h.gravatar_url(c.dbrepo.user.email)}"/>
105 </div>
105 </div>
106 ${_('Username')}: ${c.dbrepo.user.username}<br/>
106 ${_('Username')}: ${c.dbrepo.user.username}<br/>
107 ${_('Name')}: ${c.dbrepo.user.name} ${c.dbrepo.user.lastname}<br/>
107 ${_('Name')}: ${c.dbrepo.user.name} ${c.dbrepo.user.lastname}<br/>
108 ${_('Email')}: <a href="mailto:${c.dbrepo.user.email}">${c.dbrepo.user.email}</a>
108 ${_('Email')}: <a href="mailto:${c.dbrepo.user.email}">${c.dbrepo.user.email}</a>
109 </div>
109 </div>
110 </div>
110 </div>
111
111
112 <div class="field">
112 <div class="field">
113 <div class="label">
113 <div class="label">
114 <label>${_('Last change')}:</label>
114 <label>${_('Last change')}:</label>
115 </div>
115 </div>
116 <div class="input-short">
116 <div class="input-short">
117 <b>${'r%s:%s' % (h.get_changeset_safe(c.rhodecode_repo,'tip').revision,
117 <b>${'r%s:%s' % (h.get_changeset_safe(c.rhodecode_repo,'tip').revision,
118 h.get_changeset_safe(c.rhodecode_repo,'tip').short_id)}</b> -
118 h.get_changeset_safe(c.rhodecode_repo,'tip').short_id)}</b> -
119 <span class="tooltip" title="${c.rhodecode_repo.last_change}">
119 <span class="tooltip" title="${c.rhodecode_repo.last_change}">
120 ${h.age(c.rhodecode_repo.last_change)}</span><br/>
120 ${h.age(c.rhodecode_repo.last_change)}</span><br/>
121 ${_('by')} ${h.get_changeset_safe(c.rhodecode_repo,'tip').author}
121 ${_('by')} ${h.get_changeset_safe(c.rhodecode_repo,'tip').author}
122
122
123 </div>
123 </div>
124 </div>
124 </div>
125
125
126 <div class="field">
126 <div class="field">
127 <div class="label">
127 <div class="label">
128 <label>${_('Clone url')}:</label>
128 <label>${_('Clone url')}:</label>
129 </div>
129 </div>
130 <div class="input-short">
130 <div class="input-short">
131 <input type="text" id="clone_url" readonly="readonly" value="${c.rhodecode_repo.alias} clone ${c.clone_repo_url}" size="70"/>
131 <input type="text" id="clone_url" readonly="readonly" value="${c.rhodecode_repo.alias} clone ${c.clone_repo_url}" size="70"/>
132 </div>
132 </div>
133 </div>
133 </div>
134
134
135 <div class="field">
135 <div class="field">
136 <div class="label">
136 <div class="label">
137 <label>${_('Trending source files')}:</label>
137 <label>${_('Trending source files')}:</label>
138 </div>
138 </div>
139 <div class="input-short">
139 <div class="input-short">
140 <div id="lang_stats"></div>
140 <div id="lang_stats"></div>
141 </div>
141 </div>
142 </div>
142 </div>
143
143
144 <div class="field">
144 <div class="field">
145 <div class="label">
145 <div class="label">
146 <label>${_('Download')}:</label>
146 <label>${_('Download')}:</label>
147 </div>
147 </div>
148 <div class="input-short">
148 <div class="input-short">
149 %if len(c.rhodecode_repo.revisions) == 0:
149 %if len(c.rhodecode_repo.revisions) == 0:
150 ${_('There are no downloads yet')}
150 ${_('There are no downloads yet')}
151 %elif c.enable_downloads is False:
151 %elif c.enable_downloads is False:
152 ${_('Downloads are disabled for this repository')}
152 ${_('Downloads are disabled for this repository')}
153 %if h.HasPermissionAll('hg.admin')('enable stats on from summary'):
153 %if h.HasPermissionAll('hg.admin')('enable stats on from summary'):
154 [${h.link_to(_('enable'),h.url('edit_repo',repo_name=c.repo_name))}]
154 ${h.link_to(_('enable'),h.url('edit_repo',repo_name=c.repo_name),class_="ui-button-small")}
155 %endif
155 %endif
156 %else:
156 %else:
157 ${h.select('download_options',c.rhodecode_repo.get_changeset().raw_id,c.download_options)}
157 ${h.select('download_options',c.rhodecode_repo.get_changeset().raw_id,c.download_options)}
158 %for cnt,archive in enumerate(c.rhodecode_repo._get_archives()):
158 %for cnt,archive in enumerate(c.rhodecode_repo._get_archives()):
159 %if cnt >=1:
159 %if cnt >=1:
160 |
160 |
161 %endif
161 %endif
162 <span class="tooltip" title="${_('Download %s as %s') %('tip',archive['type'])}"
162 <span class="tooltip" title="${_('Download %s as %s') %('tip',archive['type'])}"
163 id="${archive['type']+'_link'}">${h.link_to(archive['type'],
163 id="${archive['type']+'_link'}">${h.link_to(archive['type'],
164 h.url('files_archive_home',repo_name=c.dbrepo.repo_name,
164 h.url('files_archive_home',repo_name=c.dbrepo.repo_name,
165 fname='tip'+archive['extension']),class_="archive_icon")}</span>
165 fname='tip'+archive['extension']),class_="archive_icon")}</span>
166 %endfor
166 %endfor
167 <span style="vertical-align: bottom">
167 <span style="vertical-align: bottom">
168 <input id="archive_subrepos" type="checkbox" name="subrepos"/> <span class="tooltip" title="${_('Check this to download archive with subrepos')}" >${_('with subrepos')}</span>
168 <input id="archive_subrepos" type="checkbox" name="subrepos"/> <span class="tooltip" title="${_('Check this to download archive with subrepos')}" >${_('with subrepos')}</span>
169 </span>
169 </span>
170 %endif
170 %endif
171 </div>
171 </div>
172 </div>
172 </div>
173
173
174 <div class="field">
174 <div class="field">
175 <div class="label">
175 <div class="label">
176 <label>${_('Feeds')}:</label>
176 <label>${_('Feeds')}:</label>
177 </div>
177 </div>
178 <div class="input-short">
178 <div class="input-short">
179 %if c.rhodecode_user.username != 'default':
179 %if c.rhodecode_user.username != 'default':
180 ${h.link_to(_('RSS'),h.url('rss_feed_home',repo_name=c.dbrepo.repo_name,api_key=c.rhodecode_user.api_key),class_='rss_icon')}
180 ${h.link_to(_('RSS'),h.url('rss_feed_home',repo_name=c.dbrepo.repo_name,api_key=c.rhodecode_user.api_key),class_='rss_icon')}
181 ${h.link_to(_('Atom'),h.url('atom_feed_home',repo_name=c.dbrepo.repo_name,api_key=c.rhodecode_user.api_key),class_='atom_icon')}
181 ${h.link_to(_('Atom'),h.url('atom_feed_home',repo_name=c.dbrepo.repo_name,api_key=c.rhodecode_user.api_key),class_='atom_icon')}
182 %else:
182 %else:
183 ${h.link_to(_('RSS'),h.url('rss_feed_home',repo_name=c.dbrepo.repo_name),class_='rss_icon')}
183 ${h.link_to(_('RSS'),h.url('rss_feed_home',repo_name=c.dbrepo.repo_name),class_='rss_icon')}
184 ${h.link_to(_('Atom'),h.url('atom_feed_home',repo_name=c.dbrepo.repo_name),class_='atom_icon')}
184 ${h.link_to(_('Atom'),h.url('atom_feed_home',repo_name=c.dbrepo.repo_name),class_='atom_icon')}
185 %endif
185 %endif
186 </div>
186 </div>
187 </div>
187 </div>
188 </div>
188 </div>
189 </div>
189 </div>
190 <script type="text/javascript">
190 <script type="text/javascript">
191 YUE.onDOMReady(function(e){
191 YUE.onDOMReady(function(e){
192 id = 'clone_url';
192 id = 'clone_url';
193 YUE.on(id,'click',function(e){
193 YUE.on(id,'click',function(e){
194 if(YUD.hasClass(id,'selected')){
194 if(YUD.hasClass(id,'selected')){
195 return
195 return
196 }
196 }
197 else{
197 else{
198 YUD.addClass(id,'selected');
198 YUD.addClass(id,'selected');
199 YUD.get(id).select();
199 YUD.get(id).select();
200 }
200 }
201
201
202 })
202 })
203 })
203 })
204 var data = ${c.trending_languages|n};
204 var data = ${c.trending_languages|n};
205 var total = 0;
205 var total = 0;
206 var no_data = true;
206 var no_data = true;
207 for (k in data){
207 for (k in data){
208 total += data[k].count;
208 total += data[k].count;
209 no_data = false;
209 no_data = false;
210 }
210 }
211 var tbl = document.createElement('table');
211 var tbl = document.createElement('table');
212 tbl.setAttribute('class','trending_language_tbl');
212 tbl.setAttribute('class','trending_language_tbl');
213 var cnt = 0;
213 var cnt = 0;
214 for (k in data){
214 for (k in data){
215 cnt += 1;
215 cnt += 1;
216 var hide = cnt>2;
216 var hide = cnt>2;
217 var tr = document.createElement('tr');
217 var tr = document.createElement('tr');
218 if (hide){
218 if (hide){
219 tr.setAttribute('style','display:none');
219 tr.setAttribute('style','display:none');
220 tr.setAttribute('class','stats_hidden');
220 tr.setAttribute('class','stats_hidden');
221 }
221 }
222 var percentage = Math.round((data[k].count/total*100),2);
222 var percentage = Math.round((data[k].count/total*100),2);
223 var value = data[k].count;
223 var value = data[k].count;
224 var td1 = document.createElement('td');
224 var td1 = document.createElement('td');
225 td1.width = 150;
225 td1.width = 150;
226 var trending_language_label = document.createElement('div');
226 var trending_language_label = document.createElement('div');
227 trending_language_label.innerHTML = data[k].desc+" ("+k+")";
227 trending_language_label.innerHTML = data[k].desc+" ("+k+")";
228 td1.appendChild(trending_language_label);
228 td1.appendChild(trending_language_label);
229
229
230 var td2 = document.createElement('td');
230 var td2 = document.createElement('td');
231 td2.setAttribute('style','padding-right:14px !important');
231 td2.setAttribute('style','padding-right:14px !important');
232 var trending_language = document.createElement('div');
232 var trending_language = document.createElement('div');
233 var nr_files = value+" ${_('files')}";
233 var nr_files = value+" ${_('files')}";
234
234
235 trending_language.title = k+" "+nr_files;
235 trending_language.title = k+" "+nr_files;
236
236
237 if (percentage>22){
237 if (percentage>22){
238 trending_language.innerHTML = "<b style='font-size:0.8em'>"+percentage+"% "+nr_files+ "</b>";
238 trending_language.innerHTML = "<b style='font-size:0.8em'>"+percentage+"% "+nr_files+ "</b>";
239 }
239 }
240 else{
240 else{
241 trending_language.innerHTML = "<b style='font-size:0.8em'>"+percentage+"%</b>";
241 trending_language.innerHTML = "<b style='font-size:0.8em'>"+percentage+"%</b>";
242 }
242 }
243
243
244 trending_language.setAttribute("class", 'trending_language top-right-rounded-corner bottom-right-rounded-corner');
244 trending_language.setAttribute("class", 'trending_language top-right-rounded-corner bottom-right-rounded-corner');
245 trending_language.style.width=percentage+"%";
245 trending_language.style.width=percentage+"%";
246 td2.appendChild(trending_language);
246 td2.appendChild(trending_language);
247
247
248 tr.appendChild(td1);
248 tr.appendChild(td1);
249 tr.appendChild(td2);
249 tr.appendChild(td2);
250 tbl.appendChild(tr);
250 tbl.appendChild(tr);
251 if(cnt == 3){
251 if(cnt == 3){
252 var show_more = document.createElement('tr');
252 var show_more = document.createElement('tr');
253 var td = document.createElement('td');
253 var td = document.createElement('td');
254 lnk = document.createElement('a');
254 lnk = document.createElement('a');
255
255
256 lnk.href='#';
256 lnk.href='#';
257 lnk.innerHTML = "${_('show more')}";
257 lnk.innerHTML = "${_('show more')}";
258 lnk.id='code_stats_show_more';
258 lnk.id='code_stats_show_more';
259 td.appendChild(lnk);
259 td.appendChild(lnk);
260
260
261 show_more.appendChild(td);
261 show_more.appendChild(td);
262 show_more.appendChild(document.createElement('td'));
262 show_more.appendChild(document.createElement('td'));
263 tbl.appendChild(show_more);
263 tbl.appendChild(show_more);
264 }
264 }
265
265
266 }
266 }
267 if(no_data){
267 if(no_data){
268 var tr = document.createElement('tr');
268 var tr = document.createElement('tr');
269 var td1 = document.createElement('td');
269 var td1 = document.createElement('td');
270 td1.innerHTML = "${c.no_data_msg}";
270 td1.innerHTML = "${c.no_data_msg}";
271 tr.appendChild(td1);
271 tr.appendChild(td1);
272 tbl.appendChild(tr);
272 tbl.appendChild(tr);
273 }
273 }
274 YUD.get('lang_stats').appendChild(tbl);
274 YUD.get('lang_stats').appendChild(tbl);
275 YUE.on('code_stats_show_more','click',function(){
275 YUE.on('code_stats_show_more','click',function(){
276 l = YUD.getElementsByClassName('stats_hidden')
276 l = YUD.getElementsByClassName('stats_hidden')
277 for (e in l){
277 for (e in l){
278 YUD.setStyle(l[e],'display','');
278 YUD.setStyle(l[e],'display','');
279 };
279 };
280 YUD.setStyle(YUD.get('code_stats_show_more'),
280 YUD.setStyle(YUD.get('code_stats_show_more'),
281 'display','none');
281 'display','none');
282 })
282 })
283
283
284 var tmpl_links = {}
284 var tmpl_links = {}
285 %for cnt,archive in enumerate(c.rhodecode_repo._get_archives()):
285 %for cnt,archive in enumerate(c.rhodecode_repo._get_archives()):
286 tmpl_links['${archive['type']}'] = '${h.link_to(archive['type'],
286 tmpl_links['${archive['type']}'] = '${h.link_to(archive['type'],
287 h.url('files_archive_home',repo_name=c.dbrepo.repo_name,
287 h.url('files_archive_home',repo_name=c.dbrepo.repo_name,
288 fname='__CS__'+archive['extension'],subrepos='__SUB__'),class_="archive_icon")}';
288 fname='__CS__'+archive['extension'],subrepos='__SUB__'),class_="archive_icon")}';
289 %endfor
289 %endfor
290
290
291 YUE.on(['download_options','archive_subrepos'],'change',function(e){
291 YUE.on(['download_options','archive_subrepos'],'change',function(e){
292 var sm = YUD.get('download_options');
292 var sm = YUD.get('download_options');
293 var new_cs = sm.options[sm.selectedIndex];
293 var new_cs = sm.options[sm.selectedIndex];
294
294
295 for(k in tmpl_links){
295 for(k in tmpl_links){
296 var s = YUD.get(k+'_link');
296 var s = YUD.get(k+'_link');
297 title_tmpl = "${_('Download %s as %s') % ('__CS_NAME__','__CS_EXT__')}";
297 title_tmpl = "${_('Download %s as %s') % ('__CS_NAME__','__CS_EXT__')}";
298 s.title = title_tmpl.replace('__CS_NAME__',new_cs.text);
298 s.title = title_tmpl.replace('__CS_NAME__',new_cs.text);
299 s.title = s.title.replace('__CS_EXT__',k);
299 s.title = s.title.replace('__CS_EXT__',k);
300 var url = tmpl_links[k].replace('__CS__',new_cs.value);
300 var url = tmpl_links[k].replace('__CS__',new_cs.value);
301 var subrepos = YUD.get('archive_subrepos').checked
301 var subrepos = YUD.get('archive_subrepos').checked
302 url = url.replace('__SUB__',subrepos);
302 url = url.replace('__SUB__',subrepos);
303 s.innerHTML = url
303 s.innerHTML = url
304 }
304 }
305 });
305 });
306 </script>
306 </script>
307 </div>
307 </div>
308
308
309 <div class="box box-right" style="min-height:455px">
309 <div class="box box-right" style="min-height:455px">
310 <!-- box / title -->
310 <!-- box / title -->
311 <div class="title">
311 <div class="title">
312 <h5>${_('Commit activity by day / author')}</h5>
312 <h5>${_('Commit activity by day / author')}</h5>
313 </div>
313 </div>
314
314
315 <div class="graph">
315 <div class="graph">
316 <div style="padding:0 10px 10px 15px;font-size: 1.2em;">
316 <div style="padding:0 10px 10px 15px;font-size: 1.2em;">
317 %if c.no_data:
317 %if c.no_data:
318 ${c.no_data_msg}
318 ${c.no_data_msg}
319 %if h.HasPermissionAll('hg.admin')('enable stats on from summary'):
319 %if h.HasPermissionAll('hg.admin')('enable stats on from summary'):
320 [${h.link_to(_('enable'),h.url('edit_repo',repo_name=c.repo_name))}]
320 ${h.link_to(_('enable'),h.url('edit_repo',repo_name=c.repo_name),class_="ui-button-small")}
321 %endif
321 %endif
322
322
323 %else:
323 %else:
324 ${_('Loaded in')} ${c.stats_percentage} %
324 ${_('Loaded in')} ${c.stats_percentage} %
325 %endif
325 %endif
326 </div>
326 </div>
327 <div id="commit_history" style="width:450px;height:300px;float:left"></div>
327 <div id="commit_history" style="width:450px;height:300px;float:left"></div>
328 <div style="clear: both;height: 10px"></div>
328 <div style="clear: both;height: 10px"></div>
329 <div id="overview" style="width:450px;height:100px;float:left"></div>
329 <div id="overview" style="width:450px;height:100px;float:left"></div>
330
330
331 <div id="legend_data" style="clear:both;margin-top:10px;">
331 <div id="legend_data" style="clear:both;margin-top:10px;">
332 <div id="legend_container"></div>
332 <div id="legend_container"></div>
333 <div id="legend_choices">
333 <div id="legend_choices">
334 <table id="legend_choices_tables" style="font-size:smaller;color:#545454"></table>
334 <table id="legend_choices_tables" style="font-size:smaller;color:#545454"></table>
335 </div>
335 </div>
336 </div>
336 </div>
337 <script type="text/javascript">
337 <script type="text/javascript">
338 /**
338 /**
339 * Plots summary graph
339 * Plots summary graph
340 *
340 *
341 * @class SummaryPlot
341 * @class SummaryPlot
342 * @param {from} initial from for detailed graph
342 * @param {from} initial from for detailed graph
343 * @param {to} initial to for detailed graph
343 * @param {to} initial to for detailed graph
344 * @param {dataset}
344 * @param {dataset}
345 * @param {overview_dataset}
345 * @param {overview_dataset}
346 */
346 */
347 function SummaryPlot(from,to,dataset,overview_dataset) {
347 function SummaryPlot(from,to,dataset,overview_dataset) {
348 var initial_ranges = {
348 var initial_ranges = {
349 "xaxis":{
349 "xaxis":{
350 "from":from,
350 "from":from,
351 "to":to,
351 "to":to,
352 },
352 },
353 };
353 };
354 var dataset = dataset;
354 var dataset = dataset;
355 var overview_dataset = [overview_dataset];
355 var overview_dataset = [overview_dataset];
356 var choiceContainer = YUD.get("legend_choices");
356 var choiceContainer = YUD.get("legend_choices");
357 var choiceContainerTable = YUD.get("legend_choices_tables");
357 var choiceContainerTable = YUD.get("legend_choices_tables");
358 var plotContainer = YUD.get('commit_history');
358 var plotContainer = YUD.get('commit_history');
359 var overviewContainer = YUD.get('overview');
359 var overviewContainer = YUD.get('overview');
360
360
361 var plot_options = {
361 var plot_options = {
362 bars: {show:true,align:'center',lineWidth:4},
362 bars: {show:true,align:'center',lineWidth:4},
363 legend: {show:true, container:"legend_container"},
363 legend: {show:true, container:"legend_container"},
364 points: {show:true,radius:0,fill:false},
364 points: {show:true,radius:0,fill:false},
365 yaxis: {tickDecimals:0,},
365 yaxis: {tickDecimals:0,},
366 xaxis: {
366 xaxis: {
367 mode: "time",
367 mode: "time",
368 timeformat: "%d/%m",
368 timeformat: "%d/%m",
369 min:from,
369 min:from,
370 max:to,
370 max:to,
371 },
371 },
372 grid: {
372 grid: {
373 hoverable: true,
373 hoverable: true,
374 clickable: true,
374 clickable: true,
375 autoHighlight:true,
375 autoHighlight:true,
376 color: "#999"
376 color: "#999"
377 },
377 },
378 //selection: {mode: "x"}
378 //selection: {mode: "x"}
379 };
379 };
380 var overview_options = {
380 var overview_options = {
381 legend:{show:false},
381 legend:{show:false},
382 bars: {show:true,barWidth: 2,},
382 bars: {show:true,barWidth: 2,},
383 shadowSize: 0,
383 shadowSize: 0,
384 xaxis: {mode: "time", timeformat: "%d/%m/%y",},
384 xaxis: {mode: "time", timeformat: "%d/%m/%y",},
385 yaxis: {ticks: 3, min: 0,tickDecimals:0,},
385 yaxis: {ticks: 3, min: 0,tickDecimals:0,},
386 grid: {color: "#999",},
386 grid: {color: "#999",},
387 selection: {mode: "x"}
387 selection: {mode: "x"}
388 };
388 };
389
389
390 /**
390 /**
391 *get dummy data needed in few places
391 *get dummy data needed in few places
392 */
392 */
393 function getDummyData(label){
393 function getDummyData(label){
394 return {"label":label,
394 return {"label":label,
395 "data":[{"time":0,
395 "data":[{"time":0,
396 "commits":0,
396 "commits":0,
397 "added":0,
397 "added":0,
398 "changed":0,
398 "changed":0,
399 "removed":0,
399 "removed":0,
400 }],
400 }],
401 "schema":["commits"],
401 "schema":["commits"],
402 "color":'#ffffff',
402 "color":'#ffffff',
403 }
403 }
404 }
404 }
405
405
406 /**
406 /**
407 * generate checkboxes accordindly to data
407 * generate checkboxes accordindly to data
408 * @param keys
408 * @param keys
409 * @returns
409 * @returns
410 */
410 */
411 function generateCheckboxes(data) {
411 function generateCheckboxes(data) {
412 //append checkboxes
412 //append checkboxes
413 var i = 0;
413 var i = 0;
414 choiceContainerTable.innerHTML = '';
414 choiceContainerTable.innerHTML = '';
415 for(var pos in data) {
415 for(var pos in data) {
416
416
417 data[pos].color = i;
417 data[pos].color = i;
418 i++;
418 i++;
419 if(data[pos].label != ''){
419 if(data[pos].label != ''){
420 choiceContainerTable.innerHTML += '<tr><td>'+
420 choiceContainerTable.innerHTML += '<tr><td>'+
421 '<input type="checkbox" name="' + data[pos].label +'" checked="checked" />'
421 '<input type="checkbox" name="' + data[pos].label +'" checked="checked" />'
422 +data[pos].label+
422 +data[pos].label+
423 '</td></tr>';
423 '</td></tr>';
424 }
424 }
425 }
425 }
426 }
426 }
427
427
428 /**
428 /**
429 * ToolTip show
429 * ToolTip show
430 */
430 */
431 function showTooltip(x, y, contents) {
431 function showTooltip(x, y, contents) {
432 var div=document.getElementById('tooltip');
432 var div=document.getElementById('tooltip');
433 if(!div) {
433 if(!div) {
434 div = document.createElement('div');
434 div = document.createElement('div');
435 div.id="tooltip";
435 div.id="tooltip";
436 div.style.position="absolute";
436 div.style.position="absolute";
437 div.style.border='1px solid #fdd';
437 div.style.border='1px solid #fdd';
438 div.style.padding='2px';
438 div.style.padding='2px';
439 div.style.backgroundColor='#fee';
439 div.style.backgroundColor='#fee';
440 document.body.appendChild(div);
440 document.body.appendChild(div);
441 }
441 }
442 YUD.setStyle(div, 'opacity', 0);
442 YUD.setStyle(div, 'opacity', 0);
443 div.innerHTML = contents;
443 div.innerHTML = contents;
444 div.style.top=(y + 5) + "px";
444 div.style.top=(y + 5) + "px";
445 div.style.left=(x + 5) + "px";
445 div.style.left=(x + 5) + "px";
446
446
447 var anim = new YAHOO.util.Anim(div, {opacity: {to: 0.8}}, 0.2);
447 var anim = new YAHOO.util.Anim(div, {opacity: {to: 0.8}}, 0.2);
448 anim.animate();
448 anim.animate();
449 }
449 }
450
450
451 /**
451 /**
452 * This function will detect if selected period has some changesets
452 * This function will detect if selected period has some changesets
453 for this user if it does this data is then pushed for displaying
453 for this user if it does this data is then pushed for displaying
454 Additionally it will only display users that are selected by the checkbox
454 Additionally it will only display users that are selected by the checkbox
455 */
455 */
456 function getDataAccordingToRanges(ranges) {
456 function getDataAccordingToRanges(ranges) {
457
457
458 var data = [];
458 var data = [];
459 var new_dataset = {};
459 var new_dataset = {};
460 var keys = [];
460 var keys = [];
461 var max_commits = 0;
461 var max_commits = 0;
462 for(var key in dataset){
462 for(var key in dataset){
463
463
464 for(var ds in dataset[key].data){
464 for(var ds in dataset[key].data){
465 commit_data = dataset[key].data[ds];
465 commit_data = dataset[key].data[ds];
466 if (commit_data.time >= ranges.xaxis.from && commit_data.time <= ranges.xaxis.to){
466 if (commit_data.time >= ranges.xaxis.from && commit_data.time <= ranges.xaxis.to){
467
467
468 if(new_dataset[key] === undefined){
468 if(new_dataset[key] === undefined){
469 new_dataset[key] = {data:[],schema:["commits"],label:key};
469 new_dataset[key] = {data:[],schema:["commits"],label:key};
470 }
470 }
471 new_dataset[key].data.push(commit_data);
471 new_dataset[key].data.push(commit_data);
472 }
472 }
473 }
473 }
474 if (new_dataset[key] !== undefined){
474 if (new_dataset[key] !== undefined){
475 data.push(new_dataset[key]);
475 data.push(new_dataset[key]);
476 }
476 }
477 }
477 }
478
478
479 if (data.length > 0){
479 if (data.length > 0){
480 return data;
480 return data;
481 }
481 }
482 else{
482 else{
483 //just return dummy data for graph to plot itself
483 //just return dummy data for graph to plot itself
484 return [getDummyData('')];
484 return [getDummyData('')];
485 }
485 }
486 }
486 }
487
487
488 /**
488 /**
489 * redraw using new checkbox data
489 * redraw using new checkbox data
490 */
490 */
491 function plotchoiced(e,args){
491 function plotchoiced(e,args){
492 var cur_data = args[0];
492 var cur_data = args[0];
493 var cur_ranges = args[1];
493 var cur_ranges = args[1];
494
494
495 var new_data = [];
495 var new_data = [];
496 var inputs = choiceContainer.getElementsByTagName("input");
496 var inputs = choiceContainer.getElementsByTagName("input");
497
497
498 //show only checked labels
498 //show only checked labels
499 for(var i=0; i<inputs.length; i++) {
499 for(var i=0; i<inputs.length; i++) {
500 var checkbox_key = inputs[i].name;
500 var checkbox_key = inputs[i].name;
501
501
502 if(inputs[i].checked){
502 if(inputs[i].checked){
503 for(var d in cur_data){
503 for(var d in cur_data){
504 if(cur_data[d].label == checkbox_key){
504 if(cur_data[d].label == checkbox_key){
505 new_data.push(cur_data[d]);
505 new_data.push(cur_data[d]);
506 }
506 }
507 }
507 }
508 }
508 }
509 else{
509 else{
510 //push dummy data to not hide the label
510 //push dummy data to not hide the label
511 new_data.push(getDummyData(checkbox_key));
511 new_data.push(getDummyData(checkbox_key));
512 }
512 }
513 }
513 }
514
514
515 var new_options = YAHOO.lang.merge(plot_options, {
515 var new_options = YAHOO.lang.merge(plot_options, {
516 xaxis: {
516 xaxis: {
517 min: cur_ranges.xaxis.from,
517 min: cur_ranges.xaxis.from,
518 max: cur_ranges.xaxis.to,
518 max: cur_ranges.xaxis.to,
519 mode:"time",
519 mode:"time",
520 timeformat: "%d/%m",
520 timeformat: "%d/%m",
521 },
521 },
522 });
522 });
523 if (!new_data){
523 if (!new_data){
524 new_data = [[0,1]];
524 new_data = [[0,1]];
525 }
525 }
526 // do the zooming
526 // do the zooming
527 plot = YAHOO.widget.Flot(plotContainer, new_data, new_options);
527 plot = YAHOO.widget.Flot(plotContainer, new_data, new_options);
528
528
529 plot.subscribe("plotselected", plotselected);
529 plot.subscribe("plotselected", plotselected);
530
530
531 //resubscribe plothover
531 //resubscribe plothover
532 plot.subscribe("plothover", plothover);
532 plot.subscribe("plothover", plothover);
533
533
534 // don't fire event on the overview to prevent eternal loop
534 // don't fire event on the overview to prevent eternal loop
535 overview.setSelection(cur_ranges, true);
535 overview.setSelection(cur_ranges, true);
536
536
537 }
537 }
538
538
539 /**
539 /**
540 * plot only selected items from overview
540 * plot only selected items from overview
541 * @param ranges
541 * @param ranges
542 * @returns
542 * @returns
543 */
543 */
544 function plotselected(ranges,cur_data) {
544 function plotselected(ranges,cur_data) {
545 //updates the data for new plot
545 //updates the data for new plot
546 var data = getDataAccordingToRanges(ranges);
546 var data = getDataAccordingToRanges(ranges);
547 generateCheckboxes(data);
547 generateCheckboxes(data);
548
548
549 var new_options = YAHOO.lang.merge(plot_options, {
549 var new_options = YAHOO.lang.merge(plot_options, {
550 xaxis: {
550 xaxis: {
551 min: ranges.xaxis.from,
551 min: ranges.xaxis.from,
552 max: ranges.xaxis.to,
552 max: ranges.xaxis.to,
553 mode:"time",
553 mode:"time",
554 timeformat: "%d/%m",
554 timeformat: "%d/%m",
555 },
555 },
556 });
556 });
557 // do the zooming
557 // do the zooming
558 plot = YAHOO.widget.Flot(plotContainer, data, new_options);
558 plot = YAHOO.widget.Flot(plotContainer, data, new_options);
559
559
560 plot.subscribe("plotselected", plotselected);
560 plot.subscribe("plotselected", plotselected);
561
561
562 //resubscribe plothover
562 //resubscribe plothover
563 plot.subscribe("plothover", plothover);
563 plot.subscribe("plothover", plothover);
564
564
565 // don't fire event on the overview to prevent eternal loop
565 // don't fire event on the overview to prevent eternal loop
566 overview.setSelection(ranges, true);
566 overview.setSelection(ranges, true);
567
567
568 //resubscribe choiced
568 //resubscribe choiced
569 YUE.on(choiceContainer.getElementsByTagName("input"), "click", plotchoiced, [data, ranges]);
569 YUE.on(choiceContainer.getElementsByTagName("input"), "click", plotchoiced, [data, ranges]);
570 }
570 }
571
571
572 var previousPoint = null;
572 var previousPoint = null;
573
573
574 function plothover(o) {
574 function plothover(o) {
575 var pos = o.pos;
575 var pos = o.pos;
576 var item = o.item;
576 var item = o.item;
577
577
578 //YUD.get("x").innerHTML = pos.x.toFixed(2);
578 //YUD.get("x").innerHTML = pos.x.toFixed(2);
579 //YUD.get("y").innerHTML = pos.y.toFixed(2);
579 //YUD.get("y").innerHTML = pos.y.toFixed(2);
580 if (item) {
580 if (item) {
581 if (previousPoint != item.datapoint) {
581 if (previousPoint != item.datapoint) {
582 previousPoint = item.datapoint;
582 previousPoint = item.datapoint;
583
583
584 var tooltip = YUD.get("tooltip");
584 var tooltip = YUD.get("tooltip");
585 if(tooltip) {
585 if(tooltip) {
586 tooltip.parentNode.removeChild(tooltip);
586 tooltip.parentNode.removeChild(tooltip);
587 }
587 }
588 var x = item.datapoint.x.toFixed(2);
588 var x = item.datapoint.x.toFixed(2);
589 var y = item.datapoint.y.toFixed(2);
589 var y = item.datapoint.y.toFixed(2);
590
590
591 if (!item.series.label){
591 if (!item.series.label){
592 item.series.label = 'commits';
592 item.series.label = 'commits';
593 }
593 }
594 var d = new Date(x*1000);
594 var d = new Date(x*1000);
595 var fd = d.toDateString()
595 var fd = d.toDateString()
596 var nr_commits = parseInt(y);
596 var nr_commits = parseInt(y);
597
597
598 var cur_data = dataset[item.series.label].data[item.dataIndex];
598 var cur_data = dataset[item.series.label].data[item.dataIndex];
599 var added = cur_data.added;
599 var added = cur_data.added;
600 var changed = cur_data.changed;
600 var changed = cur_data.changed;
601 var removed = cur_data.removed;
601 var removed = cur_data.removed;
602
602
603 var nr_commits_suffix = " ${_('commits')} ";
603 var nr_commits_suffix = " ${_('commits')} ";
604 var added_suffix = " ${_('files added')} ";
604 var added_suffix = " ${_('files added')} ";
605 var changed_suffix = " ${_('files changed')} ";
605 var changed_suffix = " ${_('files changed')} ";
606 var removed_suffix = " ${_('files removed')} ";
606 var removed_suffix = " ${_('files removed')} ";
607
607
608
608
609 if(nr_commits == 1){nr_commits_suffix = " ${_('commit')} ";}
609 if(nr_commits == 1){nr_commits_suffix = " ${_('commit')} ";}
610 if(added==1){added_suffix=" ${_('file added')} ";}
610 if(added==1){added_suffix=" ${_('file added')} ";}
611 if(changed==1){changed_suffix=" ${_('file changed')} ";}
611 if(changed==1){changed_suffix=" ${_('file changed')} ";}
612 if(removed==1){removed_suffix=" ${_('file removed')} ";}
612 if(removed==1){removed_suffix=" ${_('file removed')} ";}
613
613
614 showTooltip(item.pageX, item.pageY, item.series.label + " on " + fd
614 showTooltip(item.pageX, item.pageY, item.series.label + " on " + fd
615 +'<br/>'+
615 +'<br/>'+
616 nr_commits + nr_commits_suffix+'<br/>'+
616 nr_commits + nr_commits_suffix+'<br/>'+
617 added + added_suffix +'<br/>'+
617 added + added_suffix +'<br/>'+
618 changed + changed_suffix + '<br/>'+
618 changed + changed_suffix + '<br/>'+
619 removed + removed_suffix + '<br/>');
619 removed + removed_suffix + '<br/>');
620 }
620 }
621 }
621 }
622 else {
622 else {
623 var tooltip = YUD.get("tooltip");
623 var tooltip = YUD.get("tooltip");
624
624
625 if(tooltip) {
625 if(tooltip) {
626 tooltip.parentNode.removeChild(tooltip);
626 tooltip.parentNode.removeChild(tooltip);
627 }
627 }
628 previousPoint = null;
628 previousPoint = null;
629 }
629 }
630 }
630 }
631
631
632 /**
632 /**
633 * MAIN EXECUTION
633 * MAIN EXECUTION
634 */
634 */
635
635
636 var data = getDataAccordingToRanges(initial_ranges);
636 var data = getDataAccordingToRanges(initial_ranges);
637 generateCheckboxes(data);
637 generateCheckboxes(data);
638
638
639 //main plot
639 //main plot
640 var plot = YAHOO.widget.Flot(plotContainer,data,plot_options);
640 var plot = YAHOO.widget.Flot(plotContainer,data,plot_options);
641
641
642 //overview
642 //overview
643 var overview = YAHOO.widget.Flot(overviewContainer,
643 var overview = YAHOO.widget.Flot(overviewContainer,
644 overview_dataset, overview_options);
644 overview_dataset, overview_options);
645
645
646 //show initial selection on overview
646 //show initial selection on overview
647 overview.setSelection(initial_ranges);
647 overview.setSelection(initial_ranges);
648
648
649 plot.subscribe("plotselected", plotselected);
649 plot.subscribe("plotselected", plotselected);
650 plot.subscribe("plothover", plothover)
650 plot.subscribe("plothover", plothover)
651
651
652 overview.subscribe("plotselected", function (ranges) {
652 overview.subscribe("plotselected", function (ranges) {
653 plot.setSelection(ranges);
653 plot.setSelection(ranges);
654 });
654 });
655
655
656 YUE.on(choiceContainer.getElementsByTagName("input"), "click", plotchoiced, [data, initial_ranges]);
656 YUE.on(choiceContainer.getElementsByTagName("input"), "click", plotchoiced, [data, initial_ranges]);
657 }
657 }
658 SummaryPlot(${c.ts_min},${c.ts_max},${c.commit_data|n},${c.overview_data|n});
658 SummaryPlot(${c.ts_min},${c.ts_max},${c.commit_data|n},${c.overview_data|n});
659 </script>
659 </script>
660
660
661 </div>
661 </div>
662 </div>
662 </div>
663
663
664 <div class="box">
664 <div class="box">
665 <div class="title">
665 <div class="title">
666 <div class="breadcrumbs">${h.link_to(_('Shortlog'),h.url('shortlog_home',repo_name=c.repo_name))}</div>
666 <div class="breadcrumbs">${h.link_to(_('Shortlog'),h.url('shortlog_home',repo_name=c.repo_name))}</div>
667 </div>
667 </div>
668 <div class="table">
668 <div class="table">
669 <div id="shortlog_data">
669 <div id="shortlog_data">
670 <%include file='../shortlog/shortlog_data.html'/>
670 <%include file='../shortlog/shortlog_data.html'/>
671 </div>
671 </div>
672 ##%if c.repo_changesets:
672 ##%if c.repo_changesets:
673 ## ${h.link_to(_('show more'),h.url('changelog_home',repo_name=c.repo_name))}
673 ## ${h.link_to(_('show more'),h.url('changelog_home',repo_name=c.repo_name))}
674 ##%endif
674 ##%endif
675 </div>
675 </div>
676 </div>
676 </div>
677 <div class="box">
677 <div class="box">
678 <div class="title">
678 <div class="title">
679 <div class="breadcrumbs">${h.link_to(_('Tags'),h.url('tags_home',repo_name=c.repo_name))}</div>
679 <div class="breadcrumbs">${h.link_to(_('Tags'),h.url('tags_home',repo_name=c.repo_name))}</div>
680 </div>
680 </div>
681 <div class="table">
681 <div class="table">
682 <%include file='../tags/tags_data.html'/>
682 <%include file='../tags/tags_data.html'/>
683 %if c.repo_changesets:
683 %if c.repo_changesets:
684 ${h.link_to(_('show more'),h.url('tags_home',repo_name=c.repo_name))}
684 ${h.link_to(_('show more'),h.url('tags_home',repo_name=c.repo_name))}
685 %endif
685 %endif
686 </div>
686 </div>
687 </div>
687 </div>
688 <div class="box">
688 <div class="box">
689 <div class="title">
689 <div class="title">
690 <div class="breadcrumbs">${h.link_to(_('Branches'),h.url('branches_home',repo_name=c.repo_name))}</div>
690 <div class="breadcrumbs">${h.link_to(_('Branches'),h.url('branches_home',repo_name=c.repo_name))}</div>
691 </div>
691 </div>
692 <div class="table">
692 <div class="table">
693 <%include file='../branches/branches_data.html'/>
693 <%include file='../branches/branches_data.html'/>
694 %if c.repo_changesets:
694 %if c.repo_changesets:
695 ${h.link_to(_('show more'),h.url('branches_home',repo_name=c.repo_name))}
695 ${h.link_to(_('show more'),h.url('branches_home',repo_name=c.repo_name))}
696 %endif
696 %endif
697 </div>
697 </div>
698 </div>
698 </div>
699
699
700 </%def>
700 </%def>
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now