##// END OF EJS Templates
Merge branch beta into stable
marcink -
r2854:d998cc84 merge rhodecode-0.0.1.4.2 default
parent child Browse files
Show More

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

@@ -1,38 +1,42 b''
1 language: python
1 language: python
2 python:
2 python:
3 - "2.5"
3 - "2.5"
4 - "2.6"
4 - "2.6"
5 - "2.7"
5 - "2.7"
6
6
7 env:
7 env:
8 - TEST_DB=sqlite:////tmp/rhodecode_test.sqlite
8 - TEST_DB=sqlite:////tmp/rhodecode_test.sqlite
9 - TEST_DB=mysql://root@127.0.0.1/rhodecode_test
9 - TEST_DB=mysql://root@127.0.0.1/rhodecode_test
10 - TEST_DB=postgresql://postgres@127.0.0.1/rhodecode_test
10 - TEST_DB=postgresql://postgres@127.0.0.1/rhodecode_test
11
11
12 services:
13 - mysql
14 - postgresql
15
12 # command to install dependencies
16 # command to install dependencies
13 before_script:
17 before_script:
14 - mysql -e 'create database rhodecode_test;'
18 - mysql -e 'create database rhodecode_test;'
15 - psql -c 'create database rhodecode_test;' -U postgres
19 - psql -c 'create database rhodecode_test;' -U postgres
16 - git --version
20 - git --version
17
21
18 before_install:
22 before_install:
19 - sudo apt-get remove git
23 - sudo apt-get remove git
20 - sudo add-apt-repository ppa:pdoes/ppa -y
24 - sudo add-apt-repository ppa:pdoes/ppa -y
21 - sudo apt-get update -y
25 - sudo apt-get update -y
22 - sudo apt-get install git -y
26 - sudo apt-get install git -y
23
27
24 install:
28 install:
25 - pip install mysql-python psycopg2 mock unittest2
29 - pip install mysql-python psycopg2 mock unittest2
26 - pip install . --use-mirrors
30 - pip install . --use-mirrors
27
31
28 # command to run tests
32 # command to run tests
29 script: nosetests
33 script: nosetests
30
34
31 notifications:
35 notifications:
32 email:
36 email:
33 - marcinkuz@gmail.com
37 - marcinkuz@gmail.com
34 irc: "irc.freenode.org#rhodecode"
38 irc: "irc.freenode.org#rhodecode"
35
39
36 branches:
40 branches:
37 only:
41 only:
38 - dev
42 - dev
@@ -1,26 +1,27 b''
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 Les Peabody <lpeabody@gmail.com>
15 Les Peabody <lpeabody@gmail.com>
16 Jonas Oberschweiber <jonas.oberschweiber@d-velop.de>
16 Jonas Oberschweiber <jonas.oberschweiber@d-velop.de>
17 Matt Zuba <matt.zuba@goodwillaz.org>
17 Matt Zuba <matt.zuba@goodwillaz.org>
18 Aras Pranckevicius <aras@unity3d.com>
18 Aras Pranckevicius <aras@unity3d.com>
19 Tony Bussieres <t.bussieres@gmail.com>
19 Tony Bussieres <t.bussieres@gmail.com>
20 Erwin Kroon <e.kroon@smartmetersolutions.nl>
20 Erwin Kroon <e.kroon@smartmetersolutions.nl>
21 nansenat16 <nansenat16@null.tw>
21 nansenat16 <nansenat16@null.tw>
22 Vincent Duvert <vincent@duvert.net>
22 Vincent Duvert <vincent@duvert.net>
23 Takumi IINO <trot.thunder@gmail.com>
23 Takumi IINO <trot.thunder@gmail.com>
24 Indra Talip <indra.talip@gmail.com>
24 Indra Talip <indra.talip@gmail.com>
25 James Rhodes <jrhodes@redpointsoftware.com.au>
25 James Rhodes <jrhodes@redpointsoftware.com.au>
26 Dominik Ruf <dominikruf@gmail.com> No newline at end of file
26 Dominik Ruf <dominikruf@gmail.com>
27 xpol <xpolife@gmail.com> No newline at end of file
@@ -1,329 +1,331 b''
1 ################################################################################
1 ################################################################################
2 ################################################################################
2 ################################################################################
3 # RhodeCode - Pylons environment configuration #
3 # RhodeCode - Pylons environment configuration #
4 # #
4 # #
5 # The %(here)s variable will be replaced with the parent directory of this file#
5 # The %(here)s variable will be replaced with the parent directory of this file#
6 ################################################################################
6 ################################################################################
7
7
8 [DEFAULT]
8 [DEFAULT]
9 debug = true
9 debug = true
10 pdebug = false
10 pdebug = false
11 ################################################################################
11 ################################################################################
12 ## Uncomment and replace with the address which should receive ##
12 ## Uncomment and replace with the address which should receive ##
13 ## any error reports after application crash ##
13 ## any error reports after application crash ##
14 ## Additionally those settings will be used by RhodeCode mailing system ##
14 ## Additionally those settings will be used by RhodeCode mailing system ##
15 ################################################################################
15 ################################################################################
16 #email_to = admin@localhost
16 #email_to = admin@localhost
17 #error_email_from = paste_error@localhost
17 #error_email_from = paste_error@localhost
18 #app_email_from = rhodecode-noreply@localhost
18 #app_email_from = rhodecode-noreply@localhost
19 #error_message =
19 #error_message =
20 #email_prefix = [RhodeCode]
20 #email_prefix = [RhodeCode]
21
21
22 #smtp_server = mail.server.com
22 #smtp_server = mail.server.com
23 #smtp_username =
23 #smtp_username =
24 #smtp_password =
24 #smtp_password =
25 #smtp_port =
25 #smtp_port =
26 #smtp_use_tls = false
26 #smtp_use_tls = false
27 #smtp_use_ssl = true
27 #smtp_use_ssl = true
28 # Specify available auth parameters here (e.g. LOGIN PLAIN CRAM-MD5, etc.)
28 # Specify available auth parameters here (e.g. LOGIN PLAIN CRAM-MD5, etc.)
29 #smtp_auth =
29 #smtp_auth =
30
30
31 [server:main]
31 [server:main]
32 ##nr of threads to spawn
32 ##nr of threads to spawn
33 #threadpool_workers = 5
33 #threadpool_workers = 5
34
34
35 ##max request before thread respawn
35 ##max request before thread respawn
36 #threadpool_max_requests = 10
36 #threadpool_max_requests = 10
37
37
38 ##option to use threads of process
38 ##option to use threads of process
39 #use_threadpool = true
39 #use_threadpool = true
40
40
41 #use = egg:Paste#http
41 #use = egg:Paste#http
42 use = egg:waitress#main
42 use = egg:waitress#main
43 host = 0.0.0.0
43 host = 0.0.0.0
44 port = 5000
44 port = 5000
45
45
46 [filter:proxy-prefix]
46 [filter:proxy-prefix]
47 # prefix middleware for rc
47 # prefix middleware for rc
48 use = egg:PasteDeploy#prefix
48 use = egg:PasteDeploy#prefix
49 prefix = /<your-prefix>
49 prefix = /<your-prefix>
50
50
51 [app:main]
51 [app:main]
52 use = egg:rhodecode
52 use = egg:rhodecode
53 #filter-with = proxy-prefix
53 #filter-with = proxy-prefix
54 full_stack = true
54 full_stack = true
55 static_files = true
55 static_files = true
56 # Optional Languages
56 # Optional Languages
57 # en, fr, ja, pt_BR, zh_CN, zh_TW
57 # en, fr, ja, pt_BR, zh_CN, zh_TW
58 lang = en
58 lang = en
59 cache_dir = %(here)s/data
59 cache_dir = %(here)s/data
60 index_dir = %(here)s/data/index
60 index_dir = %(here)s/data/index
61 app_instance_uuid = rc-develop
61 app_instance_uuid = rc-develop
62 cut_off_limit = 256000
62 cut_off_limit = 256000
63 force_https = false
63 force_https = false
64 commit_parse_limit = 25
64 commit_parse_limit = 25
65 use_gravatar = true
65 use_gravatar = true
66
66
67 ## alternative_gravatar_url allows you to use your own avatar server application
67 ## alternative_gravatar_url allows you to use your own avatar server application
68 ## the following parts of the URL will be replaced
68 ## the following parts of the URL will be replaced
69 ## {email} user email
69 ## {email} user email
70 ## {md5email} md5 hash of the user email (like at gravatar.com)
70 ## {md5email} md5 hash of the user email (like at gravatar.com)
71 ## {size} size of the image that is expected from the server application
71 ## {size} size of the image that is expected from the server application
72 ## {scheme} http/https from RhodeCode server
73 ## {netloc} network location from RhodeCode server
72 #alternative_gravatar_url = http://myavatarserver.com/getbyemail/{email}/{size}
74 #alternative_gravatar_url = http://myavatarserver.com/getbyemail/{email}/{size}
73 #alternative_gravatar_url = http://myavatarserver.com/getbymd5/{md5email}?s={size}
75 #alternative_gravatar_url = http://myavatarserver.com/getbymd5/{md5email}?s={size}
74
76
75 container_auth_enabled = false
77 container_auth_enabled = false
76 proxypass_auth_enabled = false
78 proxypass_auth_enabled = false
77 default_encoding = utf8
79 default_encoding = utf8
78
80
79 ## overwrite schema of clone url
81 ## overwrite schema of clone url
80 ## available vars:
82 ## available vars:
81 ## scheme - http/https
83 ## scheme - http/https
82 ## user - current user
84 ## user - current user
83 ## pass - password
85 ## pass - password
84 ## netloc - network location
86 ## netloc - network location
85 ## path - usually repo_name
87 ## path - usually repo_name
86
88
87 #clone_uri = {scheme}://{user}{pass}{netloc}{path}
89 #clone_uri = {scheme}://{user}{pass}{netloc}{path}
88
90
89 ## issue tracking mapping for commits messages
91 ## issue tracking mapping for commits messages
90 ## comment out issue_pat, issue_server, issue_prefix to enable
92 ## comment out issue_pat, issue_server, issue_prefix to enable
91
93
92 ## pattern to get the issues from commit messages
94 ## pattern to get the issues from commit messages
93 ## default one used here is #<numbers> with a regex passive group for `#`
95 ## default one used here is #<numbers> with a regex passive group for `#`
94 ## {id} will be all groups matched from this pattern
96 ## {id} will be all groups matched from this pattern
95
97
96 issue_pat = (?:\s*#)(\d+)
98 issue_pat = (?:\s*#)(\d+)
97
99
98 ## server url to the issue, each {id} will be replaced with match
100 ## server url to the issue, each {id} will be replaced with match
99 ## fetched from the regex and {repo} is replaced with full repository name
101 ## fetched from the regex and {repo} is replaced with full repository name
100 ## including groups {repo_name} is replaced with just name of repo
102 ## including groups {repo_name} is replaced with just name of repo
101
103
102 issue_server_link = https://myissueserver.com/{repo}/issue/{id}
104 issue_server_link = https://myissueserver.com/{repo}/issue/{id}
103
105
104 ## prefix to add to link to indicate it's an url
106 ## prefix to add to link to indicate it's an url
105 ## #314 will be replaced by <issue_prefix><id>
107 ## #314 will be replaced by <issue_prefix><id>
106
108
107 issue_prefix = #
109 issue_prefix = #
108
110
109 ## instance-id prefix
111 ## instance-id prefix
110 ## a prefix key for this instance used for cache invalidation when running
112 ## a prefix key for this instance used for cache invalidation when running
111 ## multiple instances of rhodecode, make sure it's globally unique for
113 ## multiple instances of rhodecode, make sure it's globally unique for
112 ## all running rhodecode instances. Leave empty if you don't use it
114 ## all running rhodecode instances. Leave empty if you don't use it
113 instance_id =
115 instance_id =
114
116
115 ## alternative return HTTP header for failed authentication. Default HTTP
117 ## alternative return HTTP header for failed authentication. Default HTTP
116 ## response is 401 HTTPUnauthorized. Currently HG clients have troubles with
118 ## response is 401 HTTPUnauthorized. Currently HG clients have troubles with
117 ## handling that. Set this variable to 403 to return HTTPForbidden
119 ## handling that. Set this variable to 403 to return HTTPForbidden
118 auth_ret_code =
120 auth_ret_code =
119
121
120 ####################################
122 ####################################
121 ### CELERY CONFIG ####
123 ### CELERY CONFIG ####
122 ####################################
124 ####################################
123 use_celery = false
125 use_celery = false
124 broker.host = localhost
126 broker.host = localhost
125 broker.vhost = rabbitmqhost
127 broker.vhost = rabbitmqhost
126 broker.port = 5672
128 broker.port = 5672
127 broker.user = rabbitmq
129 broker.user = rabbitmq
128 broker.password = qweqwe
130 broker.password = qweqwe
129
131
130 celery.imports = rhodecode.lib.celerylib.tasks
132 celery.imports = rhodecode.lib.celerylib.tasks
131
133
132 celery.result.backend = amqp
134 celery.result.backend = amqp
133 celery.result.dburi = amqp://
135 celery.result.dburi = amqp://
134 celery.result.serialier = json
136 celery.result.serialier = json
135
137
136 #celery.send.task.error.emails = true
138 #celery.send.task.error.emails = true
137 #celery.amqp.task.result.expires = 18000
139 #celery.amqp.task.result.expires = 18000
138
140
139 celeryd.concurrency = 2
141 celeryd.concurrency = 2
140 #celeryd.log.file = celeryd.log
142 #celeryd.log.file = celeryd.log
141 celeryd.log.level = debug
143 celeryd.log.level = debug
142 celeryd.max.tasks.per.child = 1
144 celeryd.max.tasks.per.child = 1
143
145
144 #tasks will never be sent to the queue, but executed locally instead.
146 #tasks will never be sent to the queue, but executed locally instead.
145 celery.always.eager = false
147 celery.always.eager = false
146
148
147 ####################################
149 ####################################
148 ### BEAKER CACHE ####
150 ### BEAKER CACHE ####
149 ####################################
151 ####################################
150 beaker.cache.data_dir=%(here)s/data/cache/data
152 beaker.cache.data_dir=%(here)s/data/cache/data
151 beaker.cache.lock_dir=%(here)s/data/cache/lock
153 beaker.cache.lock_dir=%(here)s/data/cache/lock
152
154
153 beaker.cache.regions=super_short_term,short_term,long_term,sql_cache_short,sql_cache_med,sql_cache_long
155 beaker.cache.regions=super_short_term,short_term,long_term,sql_cache_short,sql_cache_med,sql_cache_long
154
156
155 beaker.cache.super_short_term.type=memory
157 beaker.cache.super_short_term.type=memory
156 beaker.cache.super_short_term.expire=10
158 beaker.cache.super_short_term.expire=10
157 beaker.cache.super_short_term.key_length = 256
159 beaker.cache.super_short_term.key_length = 256
158
160
159 beaker.cache.short_term.type=memory
161 beaker.cache.short_term.type=memory
160 beaker.cache.short_term.expire=60
162 beaker.cache.short_term.expire=60
161 beaker.cache.short_term.key_length = 256
163 beaker.cache.short_term.key_length = 256
162
164
163 beaker.cache.long_term.type=memory
165 beaker.cache.long_term.type=memory
164 beaker.cache.long_term.expire=36000
166 beaker.cache.long_term.expire=36000
165 beaker.cache.long_term.key_length = 256
167 beaker.cache.long_term.key_length = 256
166
168
167 beaker.cache.sql_cache_short.type=memory
169 beaker.cache.sql_cache_short.type=memory
168 beaker.cache.sql_cache_short.expire=10
170 beaker.cache.sql_cache_short.expire=10
169 beaker.cache.sql_cache_short.key_length = 256
171 beaker.cache.sql_cache_short.key_length = 256
170
172
171 beaker.cache.sql_cache_med.type=memory
173 beaker.cache.sql_cache_med.type=memory
172 beaker.cache.sql_cache_med.expire=360
174 beaker.cache.sql_cache_med.expire=360
173 beaker.cache.sql_cache_med.key_length = 256
175 beaker.cache.sql_cache_med.key_length = 256
174
176
175 beaker.cache.sql_cache_long.type=file
177 beaker.cache.sql_cache_long.type=file
176 beaker.cache.sql_cache_long.expire=3600
178 beaker.cache.sql_cache_long.expire=3600
177 beaker.cache.sql_cache_long.key_length = 256
179 beaker.cache.sql_cache_long.key_length = 256
178
180
179 ####################################
181 ####################################
180 ### BEAKER SESSION ####
182 ### BEAKER SESSION ####
181 ####################################
183 ####################################
182 ## Type of storage used for the session, current types are
184 ## Type of storage used for the session, current types are
183 ## dbm, file, memcached, database, and memory.
185 ## dbm, file, memcached, database, and memory.
184 ## The storage uses the Container API
186 ## The storage uses the Container API
185 ## that is also used by the cache system.
187 ## that is also used by the cache system.
186
188
187 ## db session ##
189 ## db session ##
188 #beaker.session.type = ext:database
190 #beaker.session.type = ext:database
189 #beaker.session.sa.url = postgresql://postgres:qwe@localhost/rhodecode
191 #beaker.session.sa.url = postgresql://postgres:qwe@localhost/rhodecode
190 #beaker.session.table_name = db_session
192 #beaker.session.table_name = db_session
191
193
192 ## encrypted cookie client side session, good for many instances ##
194 ## encrypted cookie client side session, good for many instances ##
193 #beaker.session.type = cookie
195 #beaker.session.type = cookie
194
196
195 ## file based cookies (default) ##
197 ## file based cookies (default) ##
196 #beaker.session.type = file
198 #beaker.session.type = file
197
199
198
200
199 beaker.session.key = rhodecode
201 beaker.session.key = rhodecode
200 ## secure cookie requires AES python libraries ##
202 ## secure cookie requires AES python libraries ##
201 #beaker.session.encrypt_key = g654dcno0-9873jhgfreyu
203 #beaker.session.encrypt_key = g654dcno0-9873jhgfreyu
202 #beaker.session.validate_key = 9712sds2212c--zxc123
204 #beaker.session.validate_key = 9712sds2212c--zxc123
203 ## sets session as invalid if it haven't been accessed for given amount of time
205 ## sets session as invalid if it haven't been accessed for given amount of time
204 beaker.session.timeout = 2592000
206 beaker.session.timeout = 2592000
205 beaker.session.httponly = true
207 beaker.session.httponly = true
206 #beaker.session.cookie_path = /<your-prefix>
208 #beaker.session.cookie_path = /<your-prefix>
207
209
208 ## uncomment for https secure cookie ##
210 ## uncomment for https secure cookie ##
209 beaker.session.secure = false
211 beaker.session.secure = false
210
212
211 ## auto save the session to not to use .save() ##
213 ## auto save the session to not to use .save() ##
212 beaker.session.auto = False
214 beaker.session.auto = False
213
215
214 ## default cookie expiration time in seconds `true` expire at browser close ##
216 ## default cookie expiration time in seconds `true` expire at browser close ##
215 #beaker.session.cookie_expires = 3600
217 #beaker.session.cookie_expires = 3600
216
218
217
219
218 ################################################################################
220 ################################################################################
219 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
221 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
220 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
222 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
221 ## execute malicious code after an exception is raised. ##
223 ## execute malicious code after an exception is raised. ##
222 ################################################################################
224 ################################################################################
223 #set debug = false
225 #set debug = false
224
226
225 ##################################
227 ##################################
226 ### LOGVIEW CONFIG ###
228 ### LOGVIEW CONFIG ###
227 ##################################
229 ##################################
228 logview.sqlalchemy = #faa
230 logview.sqlalchemy = #faa
229 logview.pylons.templating = #bfb
231 logview.pylons.templating = #bfb
230 logview.pylons.util = #eee
232 logview.pylons.util = #eee
231
233
232 #########################################################
234 #########################################################
233 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
235 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
234 #########################################################
236 #########################################################
235 #sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db
237 #sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db
236 sqlalchemy.db1.url = postgresql://postgres:qwe@localhost/rhodecode
238 sqlalchemy.db1.url = postgresql://postgres:qwe@localhost/rhodecode
237 sqlalchemy.db1.echo = false
239 sqlalchemy.db1.echo = false
238 sqlalchemy.db1.pool_recycle = 3600
240 sqlalchemy.db1.pool_recycle = 3600
239 sqlalchemy.db1.convert_unicode = true
241 sqlalchemy.db1.convert_unicode = true
240
242
241 ################################
243 ################################
242 ### LOGGING CONFIGURATION ####
244 ### LOGGING CONFIGURATION ####
243 ################################
245 ################################
244 [loggers]
246 [loggers]
245 keys = root, routes, rhodecode, sqlalchemy, beaker, templates, whoosh_indexer
247 keys = root, routes, rhodecode, sqlalchemy, beaker, templates, whoosh_indexer
246
248
247 [handlers]
249 [handlers]
248 keys = console, console_sql
250 keys = console, console_sql
249
251
250 [formatters]
252 [formatters]
251 keys = generic, color_formatter, color_formatter_sql
253 keys = generic, color_formatter, color_formatter_sql
252
254
253 #############
255 #############
254 ## LOGGERS ##
256 ## LOGGERS ##
255 #############
257 #############
256 [logger_root]
258 [logger_root]
257 level = NOTSET
259 level = NOTSET
258 handlers = console
260 handlers = console
259
261
260 [logger_routes]
262 [logger_routes]
261 level = DEBUG
263 level = DEBUG
262 handlers =
264 handlers =
263 qualname = routes.middleware
265 qualname = routes.middleware
264 # "level = DEBUG" logs the route matched and routing variables.
266 # "level = DEBUG" logs the route matched and routing variables.
265 propagate = 1
267 propagate = 1
266
268
267 [logger_beaker]
269 [logger_beaker]
268 level = DEBUG
270 level = DEBUG
269 handlers =
271 handlers =
270 qualname = beaker.container
272 qualname = beaker.container
271 propagate = 1
273 propagate = 1
272
274
273 [logger_templates]
275 [logger_templates]
274 level = INFO
276 level = INFO
275 handlers =
277 handlers =
276 qualname = pylons.templating
278 qualname = pylons.templating
277 propagate = 1
279 propagate = 1
278
280
279 [logger_rhodecode]
281 [logger_rhodecode]
280 level = DEBUG
282 level = DEBUG
281 handlers =
283 handlers =
282 qualname = rhodecode
284 qualname = rhodecode
283 propagate = 1
285 propagate = 1
284
286
285 [logger_sqlalchemy]
287 [logger_sqlalchemy]
286 level = INFO
288 level = INFO
287 handlers = console_sql
289 handlers = console_sql
288 qualname = sqlalchemy.engine
290 qualname = sqlalchemy.engine
289 propagate = 0
291 propagate = 0
290
292
291 [logger_whoosh_indexer]
293 [logger_whoosh_indexer]
292 level = DEBUG
294 level = DEBUG
293 handlers =
295 handlers =
294 qualname = whoosh_indexer
296 qualname = whoosh_indexer
295 propagate = 1
297 propagate = 1
296
298
297 ##############
299 ##############
298 ## HANDLERS ##
300 ## HANDLERS ##
299 ##############
301 ##############
300
302
301 [handler_console]
303 [handler_console]
302 class = StreamHandler
304 class = StreamHandler
303 args = (sys.stderr,)
305 args = (sys.stderr,)
304 level = DEBUG
306 level = DEBUG
305 formatter = color_formatter
307 formatter = color_formatter
306
308
307 [handler_console_sql]
309 [handler_console_sql]
308 class = StreamHandler
310 class = StreamHandler
309 args = (sys.stderr,)
311 args = (sys.stderr,)
310 level = DEBUG
312 level = DEBUG
311 formatter = color_formatter_sql
313 formatter = color_formatter_sql
312
314
313 ################
315 ################
314 ## FORMATTERS ##
316 ## FORMATTERS ##
315 ################
317 ################
316
318
317 [formatter_generic]
319 [formatter_generic]
318 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
320 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
319 datefmt = %Y-%m-%d %H:%M:%S
321 datefmt = %Y-%m-%d %H:%M:%S
320
322
321 [formatter_color_formatter]
323 [formatter_color_formatter]
322 class=rhodecode.lib.colored_formatter.ColorFormatter
324 class=rhodecode.lib.colored_formatter.ColorFormatter
323 format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
325 format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
324 datefmt = %Y-%m-%d %H:%M:%S
326 datefmt = %Y-%m-%d %H:%M:%S
325
327
326 [formatter_color_formatter_sql]
328 [formatter_color_formatter_sql]
327 class=rhodecode.lib.colored_formatter.ColorFormatterSql
329 class=rhodecode.lib.colored_formatter.ColorFormatterSql
328 format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
330 format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
329 datefmt = %Y-%m-%d %H:%M:%S
331 datefmt = %Y-%m-%d %H:%M:%S
@@ -1,751 +1,772 b''
1 .. _changelog:
1 .. _changelog:
2
2
3 =========
3 =========
4 Changelog
4 Changelog
5 =========
5 =========
6
6
7
7
8 1.4.1 (**2012-09-07**)
8 1.4.2 (**2012-09-12**)
9 ----------------------
9 ----------------------
10
10
11 :status: in-progress
11 news
12 :branch: beta
12 ++++
13
14 - added option to menu to quick lock/unlock repository for users that have
15 write access to
16 - Implemented permissions for writing to repo
17 groups. Now only write access to group allows to create a repostiory
18 within that group
19 - #565 Add support for {netloc} and {scheme} to alternative_gravatar_url
20 - updated translation for zh_CN
21
22 fixes
23 +++++
24
25 - fixed visual permissions check on repos groups inside groups
26 - fixed issues with non-ascii search terms in search, and indexers
27 - fixed parsing of page number in GET parameters
28 - fixed issues with generating pull-request overview for repos with
29 bookmarks and tags, also preview doesn't loose chosen revision from
30 select dropdown
31
32 1.4.1 (**2012-09-07**)
33 ----------------------
13
34
14 news
35 news
15 ++++
36 ++++
16
37
17 - always put a comment about code-review status change even if user send
38 - always put a comment about code-review status change even if user send
18 empty data
39 empty data
19 - modified_on column saves repository update and it's going to be used
40 - modified_on column saves repository update and it's going to be used
20 later for light version of main page ref #500
41 later for light version of main page ref #500
21 - pull request notifications send much nicer emails with details about pull
42 - pull request notifications send much nicer emails with details about pull
22 request
43 request
23 - #551 show breadcrumbs in summary view for repositories inside a group
44 - #551 show breadcrumbs in summary view for repositories inside a group
24
45
25 fixes
46 fixes
26 +++++
47 +++++
27
48
28 - fixed migrations of permissions that can lead to inconsistency.
49 - fixed migrations of permissions that can lead to inconsistency.
29 Some users sent feedback that after upgrading from older versions issues
50 Some users sent feedback that after upgrading from older versions issues
30 with updating default permissions occurred. RhodeCode detects that now and
51 with updating default permissions occurred. RhodeCode detects that now and
31 resets default user permission to initial state if there is a need for that.
52 resets default user permission to initial state if there is a need for that.
32 Also forces users to set the default value for new forking permission.
53 Also forces users to set the default value for new forking permission.
33 - #535 improved apache wsgi example configuration in docs
54 - #535 improved apache wsgi example configuration in docs
34 - fixes #550 mercurial repositories comparision failed when origin repo had
55 - fixes #550 mercurial repositories comparision failed when origin repo had
35 additional not-common changesets
56 additional not-common changesets
36 - fixed status of code-review in preview windows of pull request
57 - fixed status of code-review in preview windows of pull request
37 - git forks were not initialized at bare repos
58 - git forks were not initialized at bare repos
38 - fixes #555 fixes issues with comparing non-related repositories
59 - fixes #555 fixes issues with comparing non-related repositories
39 - fixes #557 follower counter always counts up
60 - fixes #557 follower counter always counts up
40 - fixed issue #560 require push ssl checkbox wasn't shown when option was
61 - fixed issue #560 require push ssl checkbox wasn't shown when option was
41 enabled
62 enabled
42 - fixed #559
63 - fixed #559
43 - fixed issue #559 fixed bug in routing that mapped repo names with <name>_<num> in name as
64 - fixed issue #559 fixed bug in routing that mapped repo names with <name>_<num> in name as
44 if it was a request to url by repository ID
65 if it was a request to url by repository ID
45
66
46 1.4.0 (**2012-09-03**)
67 1.4.0 (**2012-09-03**)
47 ----------------------
68 ----------------------
48
69
49 news
70 news
50 ++++
71 ++++
51
72
52 - new codereview system
73 - new codereview system
53 - email map, allowing users to have multiple email addresses mapped into
74 - email map, allowing users to have multiple email addresses mapped into
54 their accounts
75 their accounts
55 - improved git-hook system. Now all actions for git are logged into journal
76 - improved git-hook system. Now all actions for git are logged into journal
56 including pushed revisions, user and IP address
77 including pushed revisions, user and IP address
57 - changed setup-app into setup-rhodecode and added default options to it.
78 - changed setup-app into setup-rhodecode and added default options to it.
58 - new git repos are created as bare now by default
79 - new git repos are created as bare now by default
59 - #464 added links to groups in permission box
80 - #464 added links to groups in permission box
60 - #465 mentions autocomplete inside comments boxes
81 - #465 mentions autocomplete inside comments boxes
61 - #469 added --update-only option to whoosh to re-index only given list
82 - #469 added --update-only option to whoosh to re-index only given list
62 of repos in index
83 of repos in index
63 - rhodecode-api CLI client
84 - rhodecode-api CLI client
64 - new git http protocol replaced buggy dulwich implementation.
85 - new git http protocol replaced buggy dulwich implementation.
65 Now based on pygrack & gitweb
86 Now based on pygrack & gitweb
66 - Improved RSS/ATOM feeds. Discoverable by browsers using proper headers, and
87 - Improved RSS/ATOM feeds. Discoverable by browsers using proper headers, and
67 reformated based on user suggestions. Additional rss/atom feeds for user
88 reformated based on user suggestions. Additional rss/atom feeds for user
68 journal
89 journal
69 - various i18n improvements
90 - various i18n improvements
70 - #478 permissions overview for admin in user edit view
91 - #478 permissions overview for admin in user edit view
71 - File view now displays small gravatars off all authors of given file
92 - File view now displays small gravatars off all authors of given file
72 - Implemented landing revisions. Each repository will get landing_rev attribute
93 - Implemented landing revisions. Each repository will get landing_rev attribute
73 that defines 'default' revision/branch for generating readme files
94 that defines 'default' revision/branch for generating readme files
74 - Implemented #509, RhodeCode enforces SSL for push/pulling if requested at
95 - Implemented #509, RhodeCode enforces SSL for push/pulling if requested at
75 earliest possible call.
96 earliest possible call.
76 - Import remote svn repositories to mercurial using hgsubversion.
97 - Import remote svn repositories to mercurial using hgsubversion.
77 - Fixed #508 RhodeCode now has a option to explicitly set forking permissions
98 - Fixed #508 RhodeCode now has a option to explicitly set forking permissions
78 - RhodeCode can use alternative server for generating avatar icons
99 - RhodeCode can use alternative server for generating avatar icons
79 - implemented repositories locking. Pull locks, push unlocks. Also can be done
100 - implemented repositories locking. Pull locks, push unlocks. Also can be done
80 via API calls
101 via API calls
81 - #538 form for permissions can handle multiple users at once
102 - #538 form for permissions can handle multiple users at once
82
103
83 fixes
104 fixes
84 +++++
105 +++++
85
106
86 - improved translations
107 - improved translations
87 - fixes issue #455 Creating an archive generates an exception on Windows
108 - fixes issue #455 Creating an archive generates an exception on Windows
88 - fixes #448 Download ZIP archive keeps file in /tmp open and results
109 - fixes #448 Download ZIP archive keeps file in /tmp open and results
89 in out of disk space
110 in out of disk space
90 - fixes issue #454 Search results under Windows include proceeding
111 - fixes issue #454 Search results under Windows include proceeding
91 backslash
112 backslash
92 - fixed issue #450. Rhodecode no longer will crash when bad revision is
113 - fixed issue #450. Rhodecode no longer will crash when bad revision is
93 present in journal data.
114 present in journal data.
94 - fix for issue #417, git execution was broken on windows for certain
115 - fix for issue #417, git execution was broken on windows for certain
95 commands.
116 commands.
96 - fixed #413. Don't disable .git directory for bare repos on deleting
117 - fixed #413. Don't disable .git directory for bare repos on deleting
97 - fixed issue #459. Changed the way of obtaining logger in reindex task.
118 - fixed issue #459. Changed the way of obtaining logger in reindex task.
98 - fixed #453 added ID field in whoosh SCHEMA that solves the issue of
119 - fixed #453 added ID field in whoosh SCHEMA that solves the issue of
99 reindexing modified files
120 reindexing modified files
100 - fixed #481 rhodecode emails are sent without Date header
121 - fixed #481 rhodecode emails are sent without Date header
101 - fixed #458 wrong count when no repos are present
122 - fixed #458 wrong count when no repos are present
102 - fixed issue #492 missing `\ No newline at end of file` test at the end of
123 - fixed issue #492 missing `\ No newline at end of file` test at the end of
103 new chunk in html diff
124 new chunk in html diff
104 - full text search now works also for commit messages
125 - full text search now works also for commit messages
105
126
106 1.3.6 (**2012-05-17**)
127 1.3.6 (**2012-05-17**)
107 ----------------------
128 ----------------------
108
129
109 news
130 news
110 ++++
131 ++++
111
132
112 - chinese traditional translation
133 - chinese traditional translation
113 - changed setup-app into setup-rhodecode and added arguments for auto-setup
134 - changed setup-app into setup-rhodecode and added arguments for auto-setup
114 mode that doesn't need user interaction
135 mode that doesn't need user interaction
115
136
116 fixes
137 fixes
117 +++++
138 +++++
118
139
119 - fixed no scm found warning
140 - fixed no scm found warning
120 - fixed __future__ import error on rcextensions
141 - fixed __future__ import error on rcextensions
121 - made simplejson required lib for speedup on JSON encoding
142 - made simplejson required lib for speedup on JSON encoding
122 - fixes #449 bad regex could get more than revisions from parsing history
143 - fixes #449 bad regex could get more than revisions from parsing history
123 - don't clear DB session when CELERY_EAGER is turned ON
144 - don't clear DB session when CELERY_EAGER is turned ON
124
145
125 1.3.5 (**2012-05-10**)
146 1.3.5 (**2012-05-10**)
126 ----------------------
147 ----------------------
127
148
128 news
149 news
129 ++++
150 ++++
130
151
131 - use ext_json for json module
152 - use ext_json for json module
132 - unified annotation view with file source view
153 - unified annotation view with file source view
133 - notification improvements, better inbox + css
154 - notification improvements, better inbox + css
134 - #419 don't strip passwords for login forms, make rhodecode
155 - #419 don't strip passwords for login forms, make rhodecode
135 more compatible with LDAP servers
156 more compatible with LDAP servers
136 - Added HTTP_X_FORWARDED_FOR as another method of extracting
157 - Added HTTP_X_FORWARDED_FOR as another method of extracting
137 IP for pull/push logs. - moved all to base controller
158 IP for pull/push logs. - moved all to base controller
138 - #415: Adding comment to changeset causes reload.
159 - #415: Adding comment to changeset causes reload.
139 Comments are now added via ajax and doesn't reload the page
160 Comments are now added via ajax and doesn't reload the page
140 - #374 LDAP config is discarded when LDAP can't be activated
161 - #374 LDAP config is discarded when LDAP can't be activated
141 - limited push/pull operations are now logged for git in the journal
162 - limited push/pull operations are now logged for git in the journal
142 - bumped mercurial to 2.2.X series
163 - bumped mercurial to 2.2.X series
143 - added support for displaying submodules in file-browser
164 - added support for displaying submodules in file-browser
144 - #421 added bookmarks in changelog view
165 - #421 added bookmarks in changelog view
145
166
146 fixes
167 fixes
147 +++++
168 +++++
148
169
149 - fixed dev-version marker for stable when served from source codes
170 - fixed dev-version marker for stable when served from source codes
150 - fixed missing permission checks on show forks page
171 - fixed missing permission checks on show forks page
151 - #418 cast to unicode fixes in notification objects
172 - #418 cast to unicode fixes in notification objects
152 - #426 fixed mention extracting regex
173 - #426 fixed mention extracting regex
153 - fixed remote-pulling for git remotes remopositories
174 - fixed remote-pulling for git remotes remopositories
154 - fixed #434: Error when accessing files or changesets of a git repository
175 - fixed #434: Error when accessing files or changesets of a git repository
155 with submodules
176 with submodules
156 - fixed issue with empty APIKEYS for users after registration ref. #438
177 - fixed issue with empty APIKEYS for users after registration ref. #438
157 - fixed issue with getting README files from git repositories
178 - fixed issue with getting README files from git repositories
158
179
159 1.3.4 (**2012-03-28**)
180 1.3.4 (**2012-03-28**)
160 ----------------------
181 ----------------------
161
182
162 news
183 news
163 ++++
184 ++++
164
185
165 - Whoosh logging is now controlled by the .ini files logging setup
186 - Whoosh logging is now controlled by the .ini files logging setup
166 - added clone-url into edit form on /settings page
187 - added clone-url into edit form on /settings page
167 - added help text into repo add/edit forms
188 - added help text into repo add/edit forms
168 - created rcextensions module with additional mappings (ref #322) and
189 - created rcextensions module with additional mappings (ref #322) and
169 post push/pull/create repo hooks callbacks
190 post push/pull/create repo hooks callbacks
170 - implemented #377 Users view for his own permissions on account page
191 - implemented #377 Users view for his own permissions on account page
171 - #399 added inheritance of permissions for users group on repos groups
192 - #399 added inheritance of permissions for users group on repos groups
172 - #401 repository group is automatically pre-selected when adding repos
193 - #401 repository group is automatically pre-selected when adding repos
173 inside a repository group
194 inside a repository group
174 - added alternative HTTP 403 response when client failed to authenticate. Helps
195 - added alternative HTTP 403 response when client failed to authenticate. Helps
175 solving issues with Mercurial and LDAP
196 solving issues with Mercurial and LDAP
176 - #402 removed group prefix from repository name when listing repositories
197 - #402 removed group prefix from repository name when listing repositories
177 inside a group
198 inside a group
178 - added gravatars into permission view and permissions autocomplete
199 - added gravatars into permission view and permissions autocomplete
179 - #347 when running multiple RhodeCode instances, properly invalidates cache
200 - #347 when running multiple RhodeCode instances, properly invalidates cache
180 for all registered servers
201 for all registered servers
181
202
182 fixes
203 fixes
183 +++++
204 +++++
184
205
185 - fixed #390 cache invalidation problems on repos inside group
206 - fixed #390 cache invalidation problems on repos inside group
186 - fixed #385 clone by ID url was loosing proxy prefix in URL
207 - fixed #385 clone by ID url was loosing proxy prefix in URL
187 - fixed some unicode problems with waitress
208 - fixed some unicode problems with waitress
188 - fixed issue with escaping < and > in changeset commits
209 - fixed issue with escaping < and > in changeset commits
189 - fixed error occurring during recursive group creation in API
210 - fixed error occurring during recursive group creation in API
190 create_repo function
211 create_repo function
191 - fixed #393 py2.5 fixes for routes url generator
212 - fixed #393 py2.5 fixes for routes url generator
192 - fixed #397 Private repository groups shows up before login
213 - fixed #397 Private repository groups shows up before login
193 - fixed #396 fixed problems with revoking users in nested groups
214 - fixed #396 fixed problems with revoking users in nested groups
194 - fixed mysql unicode issues + specified InnoDB as default engine with
215 - fixed mysql unicode issues + specified InnoDB as default engine with
195 utf8 charset
216 utf8 charset
196 - #406 trim long branch/tag names in changelog to not break UI
217 - #406 trim long branch/tag names in changelog to not break UI
197
218
198 1.3.3 (**2012-03-02**)
219 1.3.3 (**2012-03-02**)
199 ----------------------
220 ----------------------
200
221
201 news
222 news
202 ++++
223 ++++
203
224
204
225
205 fixes
226 fixes
206 +++++
227 +++++
207
228
208 - fixed some python2.5 compatibility issues
229 - fixed some python2.5 compatibility issues
209 - fixed issues with removed repos was accidentally added as groups, after
230 - fixed issues with removed repos was accidentally added as groups, after
210 full rescan of paths
231 full rescan of paths
211 - fixes #376 Cannot edit user (using container auth)
232 - fixes #376 Cannot edit user (using container auth)
212 - fixes #378 Invalid image urls on changeset screen with proxy-prefix
233 - fixes #378 Invalid image urls on changeset screen with proxy-prefix
213 configuration
234 configuration
214 - fixed initial sorting of repos inside repo group
235 - fixed initial sorting of repos inside repo group
215 - fixes issue when user tried to resubmit same permission into user/user_groups
236 - fixes issue when user tried to resubmit same permission into user/user_groups
216 - bumped beaker version that fixes #375 leap error bug
237 - bumped beaker version that fixes #375 leap error bug
217 - fixed raw_changeset for git. It was generated with hg patch headers
238 - fixed raw_changeset for git. It was generated with hg patch headers
218 - fixed vcs issue with last_changeset for filenodes
239 - fixed vcs issue with last_changeset for filenodes
219 - fixed missing commit after hook delete
240 - fixed missing commit after hook delete
220 - fixed #372 issues with git operation detection that caused a security issue
241 - fixed #372 issues with git operation detection that caused a security issue
221 for git repos
242 for git repos
222
243
223 1.3.2 (**2012-02-28**)
244 1.3.2 (**2012-02-28**)
224 ----------------------
245 ----------------------
225
246
226 news
247 news
227 ++++
248 ++++
228
249
229
250
230 fixes
251 fixes
231 +++++
252 +++++
232
253
233 - fixed git protocol issues with repos-groups
254 - fixed git protocol issues with repos-groups
234 - fixed git remote repos validator that prevented from cloning remote git repos
255 - fixed git remote repos validator that prevented from cloning remote git repos
235 - fixes #370 ending slashes fixes for repo and groups
256 - fixes #370 ending slashes fixes for repo and groups
236 - fixes #368 improved git-protocol detection to handle other clients
257 - fixes #368 improved git-protocol detection to handle other clients
237 - fixes #366 When Setting Repository Group To Blank Repo Group Wont Be
258 - fixes #366 When Setting Repository Group To Blank Repo Group Wont Be
238 Moved To Root
259 Moved To Root
239 - fixes #371 fixed issues with beaker/sqlalchemy and non-ascii cache keys
260 - fixes #371 fixed issues with beaker/sqlalchemy and non-ascii cache keys
240 - fixed #373 missing cascade drop on user_group_to_perm table
261 - fixed #373 missing cascade drop on user_group_to_perm table
241
262
242 1.3.1 (**2012-02-27**)
263 1.3.1 (**2012-02-27**)
243 ----------------------
264 ----------------------
244
265
245 news
266 news
246 ++++
267 ++++
247
268
248
269
249 fixes
270 fixes
250 +++++
271 +++++
251
272
252 - redirection loop occurs when remember-me wasn't checked during login
273 - redirection loop occurs when remember-me wasn't checked during login
253 - fixes issues with git blob history generation
274 - fixes issues with git blob history generation
254 - don't fetch branch for git in file history dropdown. Causes unneeded slowness
275 - don't fetch branch for git in file history dropdown. Causes unneeded slowness
255
276
256 1.3.0 (**2012-02-26**)
277 1.3.0 (**2012-02-26**)
257 ----------------------
278 ----------------------
258
279
259 news
280 news
260 ++++
281 ++++
261
282
262 - code review, inspired by github code-comments
283 - code review, inspired by github code-comments
263 - #215 rst and markdown README files support
284 - #215 rst and markdown README files support
264 - #252 Container-based and proxy pass-through authentication support
285 - #252 Container-based and proxy pass-through authentication support
265 - #44 branch browser. Filtering of changelog by branches
286 - #44 branch browser. Filtering of changelog by branches
266 - mercurial bookmarks support
287 - mercurial bookmarks support
267 - new hover top menu, optimized to add maximum size for important views
288 - new hover top menu, optimized to add maximum size for important views
268 - configurable clone url template with possibility to specify protocol like
289 - configurable clone url template with possibility to specify protocol like
269 ssh:// or http:// and also manually alter other parts of clone_url.
290 ssh:// or http:// and also manually alter other parts of clone_url.
270 - enabled largefiles extension by default
291 - enabled largefiles extension by default
271 - optimized summary file pages and saved a lot of unused space in them
292 - optimized summary file pages and saved a lot of unused space in them
272 - #239 option to manually mark repository as fork
293 - #239 option to manually mark repository as fork
273 - #320 mapping of commit authors to RhodeCode users
294 - #320 mapping of commit authors to RhodeCode users
274 - #304 hashes are displayed using monospace font
295 - #304 hashes are displayed using monospace font
275 - diff configuration, toggle white lines and context lines
296 - diff configuration, toggle white lines and context lines
276 - #307 configurable diffs, whitespace toggle, increasing context lines
297 - #307 configurable diffs, whitespace toggle, increasing context lines
277 - sorting on branches, tags and bookmarks using YUI datatable
298 - sorting on branches, tags and bookmarks using YUI datatable
278 - improved file filter on files page
299 - improved file filter on files page
279 - implements #330 api method for listing nodes ar particular revision
300 - implements #330 api method for listing nodes ar particular revision
280 - #73 added linking issues in commit messages to chosen issue tracker url
301 - #73 added linking issues in commit messages to chosen issue tracker url
281 based on user defined regular expression
302 based on user defined regular expression
282 - added linking of changesets in commit messages
303 - added linking of changesets in commit messages
283 - new compact changelog with expandable commit messages
304 - new compact changelog with expandable commit messages
284 - firstname and lastname are optional in user creation
305 - firstname and lastname are optional in user creation
285 - #348 added post-create repository hook
306 - #348 added post-create repository hook
286 - #212 global encoding settings is now configurable from .ini files
307 - #212 global encoding settings is now configurable from .ini files
287 - #227 added repository groups permissions
308 - #227 added repository groups permissions
288 - markdown gets codehilite extensions
309 - markdown gets codehilite extensions
289 - new API methods, delete_repositories, grante/revoke permissions for groups
310 - new API methods, delete_repositories, grante/revoke permissions for groups
290 and repos
311 and repos
291
312
292
313
293 fixes
314 fixes
294 +++++
315 +++++
295
316
296 - rewrote dbsession management for atomic operations, and better error handling
317 - rewrote dbsession management for atomic operations, and better error handling
297 - fixed sorting of repo tables
318 - fixed sorting of repo tables
298 - #326 escape of special html entities in diffs
319 - #326 escape of special html entities in diffs
299 - normalized user_name => username in api attributes
320 - normalized user_name => username in api attributes
300 - fixes #298 ldap created users with mixed case emails created conflicts
321 - fixes #298 ldap created users with mixed case emails created conflicts
301 on saving a form
322 on saving a form
302 - fixes issue when owner of a repo couldn't revoke permissions for users
323 - fixes issue when owner of a repo couldn't revoke permissions for users
303 and groups
324 and groups
304 - fixes #271 rare JSON serialization problem with statistics
325 - fixes #271 rare JSON serialization problem with statistics
305 - fixes #337 missing validation check for conflicting names of a group with a
326 - fixes #337 missing validation check for conflicting names of a group with a
306 repositories group
327 repositories group
307 - #340 fixed session problem for mysql and celery tasks
328 - #340 fixed session problem for mysql and celery tasks
308 - fixed #331 RhodeCode mangles repository names if the a repository group
329 - fixed #331 RhodeCode mangles repository names if the a repository group
309 contains the "full path" to the repositories
330 contains the "full path" to the repositories
310 - #355 RhodeCode doesn't store encrypted LDAP passwords
331 - #355 RhodeCode doesn't store encrypted LDAP passwords
311
332
312 1.2.5 (**2012-01-28**)
333 1.2.5 (**2012-01-28**)
313 ----------------------
334 ----------------------
314
335
315 news
336 news
316 ++++
337 ++++
317
338
318 fixes
339 fixes
319 +++++
340 +++++
320
341
321 - #340 Celery complains about MySQL server gone away, added session cleanup
342 - #340 Celery complains about MySQL server gone away, added session cleanup
322 for celery tasks
343 for celery tasks
323 - #341 "scanning for repositories in None" log message during Rescan was missing
344 - #341 "scanning for repositories in None" log message during Rescan was missing
324 a parameter
345 a parameter
325 - fixed creating archives with subrepos. Some hooks were triggered during that
346 - fixed creating archives with subrepos. Some hooks were triggered during that
326 operation leading to crash.
347 operation leading to crash.
327 - fixed missing email in account page.
348 - fixed missing email in account page.
328 - Reverted Mercurial to 2.0.1 for windows due to bug in Mercurial that makes
349 - Reverted Mercurial to 2.0.1 for windows due to bug in Mercurial that makes
329 forking on windows impossible
350 forking on windows impossible
330
351
331 1.2.4 (**2012-01-19**)
352 1.2.4 (**2012-01-19**)
332 ----------------------
353 ----------------------
333
354
334 news
355 news
335 ++++
356 ++++
336
357
337 - RhodeCode is bundled with mercurial series 2.0.X by default, with
358 - RhodeCode is bundled with mercurial series 2.0.X by default, with
338 full support to largefiles extension. Enabled by default in new installations
359 full support to largefiles extension. Enabled by default in new installations
339 - #329 Ability to Add/Remove Groups to/from a Repository via AP
360 - #329 Ability to Add/Remove Groups to/from a Repository via AP
340 - added requires.txt file with requirements
361 - added requires.txt file with requirements
341
362
342 fixes
363 fixes
343 +++++
364 +++++
344
365
345 - fixes db session issues with celery when emailing admins
366 - fixes db session issues with celery when emailing admins
346 - #331 RhodeCode mangles repository names if the a repository group
367 - #331 RhodeCode mangles repository names if the a repository group
347 contains the "full path" to the repositories
368 contains the "full path" to the repositories
348 - #298 Conflicting e-mail addresses for LDAP and RhodeCode users
369 - #298 Conflicting e-mail addresses for LDAP and RhodeCode users
349 - DB session cleanup after hg protocol operations, fixes issues with
370 - DB session cleanup after hg protocol operations, fixes issues with
350 `mysql has gone away` errors
371 `mysql has gone away` errors
351 - #333 doc fixes for get_repo api function
372 - #333 doc fixes for get_repo api function
352 - #271 rare JSON serialization problem with statistics enabled
373 - #271 rare JSON serialization problem with statistics enabled
353 - #337 Fixes issues with validation of repository name conflicting with
374 - #337 Fixes issues with validation of repository name conflicting with
354 a group name. A proper message is now displayed.
375 a group name. A proper message is now displayed.
355 - #292 made ldap_dn in user edit readonly, to get rid of confusion that field
376 - #292 made ldap_dn in user edit readonly, to get rid of confusion that field
356 doesn't work
377 doesn't work
357 - #316 fixes issues with web description in hgrc files
378 - #316 fixes issues with web description in hgrc files
358
379
359 1.2.3 (**2011-11-02**)
380 1.2.3 (**2011-11-02**)
360 ----------------------
381 ----------------------
361
382
362 news
383 news
363 ++++
384 ++++
364
385
365 - added option to manage repos group for non admin users
386 - added option to manage repos group for non admin users
366 - added following API methods for get_users, create_user, get_users_groups,
387 - added following API methods for get_users, create_user, get_users_groups,
367 get_users_group, create_users_group, add_user_to_users_groups, get_repos,
388 get_users_group, create_users_group, add_user_to_users_groups, get_repos,
368 get_repo, create_repo, add_user_to_repo
389 get_repo, create_repo, add_user_to_repo
369 - implements #237 added password confirmation for my account
390 - implements #237 added password confirmation for my account
370 and admin edit user.
391 and admin edit user.
371 - implements #291 email notification for global events are now sent to all
392 - implements #291 email notification for global events are now sent to all
372 administrator users, and global config email.
393 administrator users, and global config email.
373
394
374 fixes
395 fixes
375 +++++
396 +++++
376
397
377 - added option for passing auth method for smtp mailer
398 - added option for passing auth method for smtp mailer
378 - #276 issue with adding a single user with id>10 to usergroups
399 - #276 issue with adding a single user with id>10 to usergroups
379 - #277 fixes windows LDAP settings in which missing values breaks the ldap auth
400 - #277 fixes windows LDAP settings in which missing values breaks the ldap auth
380 - #288 fixes managing of repos in a group for non admin user
401 - #288 fixes managing of repos in a group for non admin user
381
402
382 1.2.2 (**2011-10-17**)
403 1.2.2 (**2011-10-17**)
383 ----------------------
404 ----------------------
384
405
385 news
406 news
386 ++++
407 ++++
387
408
388 - #226 repo groups are available by path instead of numerical id
409 - #226 repo groups are available by path instead of numerical id
389
410
390 fixes
411 fixes
391 +++++
412 +++++
392
413
393 - #259 Groups with the same name but with different parent group
414 - #259 Groups with the same name but with different parent group
394 - #260 Put repo in group, then move group to another group -> repo becomes unavailable
415 - #260 Put repo in group, then move group to another group -> repo becomes unavailable
395 - #258 RhodeCode 1.2 assumes egg folder is writable (lockfiles problems)
416 - #258 RhodeCode 1.2 assumes egg folder is writable (lockfiles problems)
396 - #265 ldap save fails sometimes on converting attributes to booleans,
417 - #265 ldap save fails sometimes on converting attributes to booleans,
397 added getter and setter into model that will prevent from this on db model level
418 added getter and setter into model that will prevent from this on db model level
398 - fixed problems with timestamps issues #251 and #213
419 - fixed problems with timestamps issues #251 and #213
399 - fixes #266 RhodeCode allows to create repo with the same name and in
420 - fixes #266 RhodeCode allows to create repo with the same name and in
400 the same parent as group
421 the same parent as group
401 - fixes #245 Rescan of the repositories on Windows
422 - fixes #245 Rescan of the repositories on Windows
402 - fixes #248 cannot edit repos inside a group on windows
423 - fixes #248 cannot edit repos inside a group on windows
403 - fixes #219 forking problems on windows
424 - fixes #219 forking problems on windows
404
425
405 1.2.1 (**2011-10-08**)
426 1.2.1 (**2011-10-08**)
406 ----------------------
427 ----------------------
407
428
408 news
429 news
409 ++++
430 ++++
410
431
411
432
412 fixes
433 fixes
413 +++++
434 +++++
414
435
415 - fixed problems with basic auth and push problems
436 - fixed problems with basic auth and push problems
416 - gui fixes
437 - gui fixes
417 - fixed logger
438 - fixed logger
418
439
419 1.2.0 (**2011-10-07**)
440 1.2.0 (**2011-10-07**)
420 ----------------------
441 ----------------------
421
442
422 news
443 news
423 ++++
444 ++++
424
445
425 - implemented #47 repository groups
446 - implemented #47 repository groups
426 - implemented #89 Can setup google analytics code from settings menu
447 - implemented #89 Can setup google analytics code from settings menu
427 - implemented #91 added nicer looking archive urls with more download options
448 - implemented #91 added nicer looking archive urls with more download options
428 like tags, branches
449 like tags, branches
429 - implemented #44 into file browsing, and added follow branch option
450 - implemented #44 into file browsing, and added follow branch option
430 - implemented #84 downloads can be enabled/disabled for each repository
451 - implemented #84 downloads can be enabled/disabled for each repository
431 - anonymous repository can be cloned without having to pass default:default
452 - anonymous repository can be cloned without having to pass default:default
432 into clone url
453 into clone url
433 - fixed #90 whoosh indexer can index chooses repositories passed in command
454 - fixed #90 whoosh indexer can index chooses repositories passed in command
434 line
455 line
435 - extended journal with day aggregates and paging
456 - extended journal with day aggregates and paging
436 - implemented #107 source code lines highlight ranges
457 - implemented #107 source code lines highlight ranges
437 - implemented #93 customizable changelog on combined revision ranges -
458 - implemented #93 customizable changelog on combined revision ranges -
438 equivalent of githubs compare view
459 equivalent of githubs compare view
439 - implemented #108 extended and more powerful LDAP configuration
460 - implemented #108 extended and more powerful LDAP configuration
440 - implemented #56 users groups
461 - implemented #56 users groups
441 - major code rewrites optimized codes for speed and memory usage
462 - major code rewrites optimized codes for speed and memory usage
442 - raw and diff downloads are now in git format
463 - raw and diff downloads are now in git format
443 - setup command checks for write access to given path
464 - setup command checks for write access to given path
444 - fixed many issues with international characters and unicode. It uses utf8
465 - fixed many issues with international characters and unicode. It uses utf8
445 decode with replace to provide less errors even with non utf8 encoded strings
466 decode with replace to provide less errors even with non utf8 encoded strings
446 - #125 added API KEY access to feeds
467 - #125 added API KEY access to feeds
447 - #109 Repository can be created from external Mercurial link (aka. remote
468 - #109 Repository can be created from external Mercurial link (aka. remote
448 repository, and manually updated (via pull) from admin panel
469 repository, and manually updated (via pull) from admin panel
449 - beta git support - push/pull server + basic view for git repos
470 - beta git support - push/pull server + basic view for git repos
450 - added followers page and forks page
471 - added followers page and forks page
451 - server side file creation (with binary file upload interface)
472 - server side file creation (with binary file upload interface)
452 and edition with commits powered by codemirror
473 and edition with commits powered by codemirror
453 - #111 file browser file finder, quick lookup files on whole file tree
474 - #111 file browser file finder, quick lookup files on whole file tree
454 - added quick login sliding menu into main page
475 - added quick login sliding menu into main page
455 - changelog uses lazy loading of affected files details, in some scenarios
476 - changelog uses lazy loading of affected files details, in some scenarios
456 this can improve speed of changelog page dramatically especially for
477 this can improve speed of changelog page dramatically especially for
457 larger repositories.
478 larger repositories.
458 - implements #214 added support for downloading subrepos in download menu.
479 - implements #214 added support for downloading subrepos in download menu.
459 - Added basic API for direct operations on rhodecode via JSON
480 - Added basic API for direct operations on rhodecode via JSON
460 - Implemented advanced hook management
481 - Implemented advanced hook management
461
482
462 fixes
483 fixes
463 +++++
484 +++++
464
485
465 - fixed file browser bug, when switching into given form revision the url was
486 - fixed file browser bug, when switching into given form revision the url was
466 not changing
487 not changing
467 - fixed propagation to error controller on simplehg and simplegit middlewares
488 - fixed propagation to error controller on simplehg and simplegit middlewares
468 - fixed error when trying to make a download on empty repository
489 - fixed error when trying to make a download on empty repository
469 - fixed problem with '[' chars in commit messages in journal
490 - fixed problem with '[' chars in commit messages in journal
470 - fixed #99 Unicode errors, on file node paths with non utf-8 characters
491 - fixed #99 Unicode errors, on file node paths with non utf-8 characters
471 - journal fork fixes
492 - journal fork fixes
472 - removed issue with space inside renamed repository after deletion
493 - removed issue with space inside renamed repository after deletion
473 - fixed strange issue on formencode imports
494 - fixed strange issue on formencode imports
474 - fixed #126 Deleting repository on Windows, rename used incompatible chars.
495 - fixed #126 Deleting repository on Windows, rename used incompatible chars.
475 - #150 fixes for errors on repositories mapped in db but corrupted in
496 - #150 fixes for errors on repositories mapped in db but corrupted in
476 filesystem
497 filesystem
477 - fixed problem with ascendant characters in realm #181
498 - fixed problem with ascendant characters in realm #181
478 - fixed problem with sqlite file based database connection pool
499 - fixed problem with sqlite file based database connection pool
479 - whoosh indexer and code stats share the same dynamic extensions map
500 - whoosh indexer and code stats share the same dynamic extensions map
480 - fixes #188 - relationship delete of repo_to_perm entry on user removal
501 - fixes #188 - relationship delete of repo_to_perm entry on user removal
481 - fixes issue #189 Trending source files shows "show more" when no more exist
502 - fixes issue #189 Trending source files shows "show more" when no more exist
482 - fixes issue #197 Relative paths for pidlocks
503 - fixes issue #197 Relative paths for pidlocks
483 - fixes issue #198 password will require only 3 chars now for login form
504 - fixes issue #198 password will require only 3 chars now for login form
484 - fixes issue #199 wrong redirection for non admin users after creating a repository
505 - fixes issue #199 wrong redirection for non admin users after creating a repository
485 - fixes issues #202, bad db constraint made impossible to attach same group
506 - fixes issues #202, bad db constraint made impossible to attach same group
486 more than one time. Affects only mysql/postgres
507 more than one time. Affects only mysql/postgres
487 - fixes #218 os.kill patch for windows was missing sig param
508 - fixes #218 os.kill patch for windows was missing sig param
488 - improved rendering of dag (they are not trimmed anymore when number of
509 - improved rendering of dag (they are not trimmed anymore when number of
489 heads exceeds 5)
510 heads exceeds 5)
490
511
491 1.1.8 (**2011-04-12**)
512 1.1.8 (**2011-04-12**)
492 ----------------------
513 ----------------------
493
514
494 news
515 news
495 ++++
516 ++++
496
517
497 - improved windows support
518 - improved windows support
498
519
499 fixes
520 fixes
500 +++++
521 +++++
501
522
502 - fixed #140 freeze of python dateutil library, since new version is python2.x
523 - fixed #140 freeze of python dateutil library, since new version is python2.x
503 incompatible
524 incompatible
504 - setup-app will check for write permission in given path
525 - setup-app will check for write permission in given path
505 - cleaned up license info issue #149
526 - cleaned up license info issue #149
506 - fixes for issues #137,#116 and problems with unicode and accented characters.
527 - fixes for issues #137,#116 and problems with unicode and accented characters.
507 - fixes crashes on gravatar, when passed in email as unicode
528 - fixes crashes on gravatar, when passed in email as unicode
508 - fixed tooltip flickering problems
529 - fixed tooltip flickering problems
509 - fixed came_from redirection on windows
530 - fixed came_from redirection on windows
510 - fixed logging modules, and sql formatters
531 - fixed logging modules, and sql formatters
511 - windows fixes for os.kill issue #133
532 - windows fixes for os.kill issue #133
512 - fixes path splitting for windows issues #148
533 - fixes path splitting for windows issues #148
513 - fixed issue #143 wrong import on migration to 1.1.X
534 - fixed issue #143 wrong import on migration to 1.1.X
514 - fixed problems with displaying binary files, thanks to Thomas Waldmann
535 - fixed problems with displaying binary files, thanks to Thomas Waldmann
515 - removed name from archive files since it's breaking ui for long repo names
536 - removed name from archive files since it's breaking ui for long repo names
516 - fixed issue with archive headers sent to browser, thanks to Thomas Waldmann
537 - fixed issue with archive headers sent to browser, thanks to Thomas Waldmann
517 - fixed compatibility for 1024px displays, and larger dpi settings, thanks to
538 - fixed compatibility for 1024px displays, and larger dpi settings, thanks to
518 Thomas Waldmann
539 Thomas Waldmann
519 - fixed issue #166 summary pager was skipping 10 revisions on second page
540 - fixed issue #166 summary pager was skipping 10 revisions on second page
520
541
521
542
522 1.1.7 (**2011-03-23**)
543 1.1.7 (**2011-03-23**)
523 ----------------------
544 ----------------------
524
545
525 news
546 news
526 ++++
547 ++++
527
548
528 fixes
549 fixes
529 +++++
550 +++++
530
551
531 - fixed (again) #136 installation support for FreeBSD
552 - fixed (again) #136 installation support for FreeBSD
532
553
533
554
534 1.1.6 (**2011-03-21**)
555 1.1.6 (**2011-03-21**)
535 ----------------------
556 ----------------------
536
557
537 news
558 news
538 ++++
559 ++++
539
560
540 fixes
561 fixes
541 +++++
562 +++++
542
563
543 - fixed #136 installation support for FreeBSD
564 - fixed #136 installation support for FreeBSD
544 - RhodeCode will check for python version during installation
565 - RhodeCode will check for python version during installation
545
566
546 1.1.5 (**2011-03-17**)
567 1.1.5 (**2011-03-17**)
547 ----------------------
568 ----------------------
548
569
549 news
570 news
550 ++++
571 ++++
551
572
552 - basic windows support, by exchanging pybcrypt into sha256 for windows only
573 - basic windows support, by exchanging pybcrypt into sha256 for windows only
553 highly inspired by idea of mantis406
574 highly inspired by idea of mantis406
554
575
555 fixes
576 fixes
556 +++++
577 +++++
557
578
558 - fixed sorting by author in main page
579 - fixed sorting by author in main page
559 - fixed crashes with diffs on binary files
580 - fixed crashes with diffs on binary files
560 - fixed #131 problem with boolean values for LDAP
581 - fixed #131 problem with boolean values for LDAP
561 - fixed #122 mysql problems thanks to striker69
582 - fixed #122 mysql problems thanks to striker69
562 - fixed problem with errors on calling raw/raw_files/annotate functions
583 - fixed problem with errors on calling raw/raw_files/annotate functions
563 with unknown revisions
584 with unknown revisions
564 - fixed returned rawfiles attachment names with international character
585 - fixed returned rawfiles attachment names with international character
565 - cleaned out docs, big thanks to Jason Harris
586 - cleaned out docs, big thanks to Jason Harris
566
587
567 1.1.4 (**2011-02-19**)
588 1.1.4 (**2011-02-19**)
568 ----------------------
589 ----------------------
569
590
570 news
591 news
571 ++++
592 ++++
572
593
573 fixes
594 fixes
574 +++++
595 +++++
575
596
576 - fixed formencode import problem on settings page, that caused server crash
597 - fixed formencode import problem on settings page, that caused server crash
577 when that page was accessed as first after server start
598 when that page was accessed as first after server start
578 - journal fixes
599 - journal fixes
579 - fixed option to access repository just by entering http://server/<repo_name>
600 - fixed option to access repository just by entering http://server/<repo_name>
580
601
581 1.1.3 (**2011-02-16**)
602 1.1.3 (**2011-02-16**)
582 ----------------------
603 ----------------------
583
604
584 news
605 news
585 ++++
606 ++++
586
607
587 - implemented #102 allowing the '.' character in username
608 - implemented #102 allowing the '.' character in username
588 - added option to access repository just by entering http://server/<repo_name>
609 - added option to access repository just by entering http://server/<repo_name>
589 - celery task ignores result for better performance
610 - celery task ignores result for better performance
590
611
591 fixes
612 fixes
592 +++++
613 +++++
593
614
594 - fixed ehlo command and non auth mail servers on smtp_lib. Thanks to
615 - fixed ehlo command and non auth mail servers on smtp_lib. Thanks to
595 apollo13 and Johan Walles
616 apollo13 and Johan Walles
596 - small fixes in journal
617 - small fixes in journal
597 - fixed problems with getting setting for celery from .ini files
618 - fixed problems with getting setting for celery from .ini files
598 - registration, password reset and login boxes share the same title as main
619 - registration, password reset and login boxes share the same title as main
599 application now
620 application now
600 - fixed #113: to high permissions to fork repository
621 - fixed #113: to high permissions to fork repository
601 - fixed problem with '[' chars in commit messages in journal
622 - fixed problem with '[' chars in commit messages in journal
602 - removed issue with space inside renamed repository after deletion
623 - removed issue with space inside renamed repository after deletion
603 - db transaction fixes when filesystem repository creation failed
624 - db transaction fixes when filesystem repository creation failed
604 - fixed #106 relation issues on databases different than sqlite
625 - fixed #106 relation issues on databases different than sqlite
605 - fixed static files paths links to use of url() method
626 - fixed static files paths links to use of url() method
606
627
607 1.1.2 (**2011-01-12**)
628 1.1.2 (**2011-01-12**)
608 ----------------------
629 ----------------------
609
630
610 news
631 news
611 ++++
632 ++++
612
633
613
634
614 fixes
635 fixes
615 +++++
636 +++++
616
637
617 - fixes #98 protection against float division of percentage stats
638 - fixes #98 protection against float division of percentage stats
618 - fixed graph bug
639 - fixed graph bug
619 - forced webhelpers version since it was making troubles during installation
640 - forced webhelpers version since it was making troubles during installation
620
641
621 1.1.1 (**2011-01-06**)
642 1.1.1 (**2011-01-06**)
622 ----------------------
643 ----------------------
623
644
624 news
645 news
625 ++++
646 ++++
626
647
627 - added force https option into ini files for easier https usage (no need to
648 - added force https option into ini files for easier https usage (no need to
628 set server headers with this options)
649 set server headers with this options)
629 - small css updates
650 - small css updates
630
651
631 fixes
652 fixes
632 +++++
653 +++++
633
654
634 - fixed #96 redirect loop on files view on repositories without changesets
655 - fixed #96 redirect loop on files view on repositories without changesets
635 - fixed #97 unicode string passed into server header in special cases (mod_wsgi)
656 - fixed #97 unicode string passed into server header in special cases (mod_wsgi)
636 and server crashed with errors
657 and server crashed with errors
637 - fixed large tooltips problems on main page
658 - fixed large tooltips problems on main page
638 - fixed #92 whoosh indexer is more error proof
659 - fixed #92 whoosh indexer is more error proof
639
660
640 1.1.0 (**2010-12-18**)
661 1.1.0 (**2010-12-18**)
641 ----------------------
662 ----------------------
642
663
643 news
664 news
644 ++++
665 ++++
645
666
646 - rewrite of internals for vcs >=0.1.10
667 - rewrite of internals for vcs >=0.1.10
647 - uses mercurial 1.7 with dotencode disabled for maintaining compatibility
668 - uses mercurial 1.7 with dotencode disabled for maintaining compatibility
648 with older clients
669 with older clients
649 - anonymous access, authentication via ldap
670 - anonymous access, authentication via ldap
650 - performance upgrade for cached repos list - each repository has its own
671 - performance upgrade for cached repos list - each repository has its own
651 cache that's invalidated when needed.
672 cache that's invalidated when needed.
652 - performance upgrades on repositories with large amount of commits (20K+)
673 - performance upgrades on repositories with large amount of commits (20K+)
653 - main page quick filter for filtering repositories
674 - main page quick filter for filtering repositories
654 - user dashboards with ability to follow chosen repositories actions
675 - user dashboards with ability to follow chosen repositories actions
655 - sends email to admin on new user registration
676 - sends email to admin on new user registration
656 - added cache/statistics reset options into repository settings
677 - added cache/statistics reset options into repository settings
657 - more detailed action logger (based on hooks) with pushed changesets lists
678 - more detailed action logger (based on hooks) with pushed changesets lists
658 and options to disable those hooks from admin panel
679 and options to disable those hooks from admin panel
659 - introduced new enhanced changelog for merges that shows more accurate results
680 - introduced new enhanced changelog for merges that shows more accurate results
660 - new improved and faster code stats (based on pygments lexers mapping tables,
681 - new improved and faster code stats (based on pygments lexers mapping tables,
661 showing up to 10 trending sources for each repository. Additionally stats
682 showing up to 10 trending sources for each repository. Additionally stats
662 can be disabled in repository settings.
683 can be disabled in repository settings.
663 - gui optimizations, fixed application width to 1024px
684 - gui optimizations, fixed application width to 1024px
664 - added cut off (for large files/changesets) limit into config files
685 - added cut off (for large files/changesets) limit into config files
665 - whoosh, celeryd, upgrade moved to paster command
686 - whoosh, celeryd, upgrade moved to paster command
666 - other than sqlite database backends can be used
687 - other than sqlite database backends can be used
667
688
668 fixes
689 fixes
669 +++++
690 +++++
670
691
671 - fixes #61 forked repo was showing only after cache expired
692 - fixes #61 forked repo was showing only after cache expired
672 - fixes #76 no confirmation on user deletes
693 - fixes #76 no confirmation on user deletes
673 - fixes #66 Name field misspelled
694 - fixes #66 Name field misspelled
674 - fixes #72 block user removal when he owns repositories
695 - fixes #72 block user removal when he owns repositories
675 - fixes #69 added password confirmation fields
696 - fixes #69 added password confirmation fields
676 - fixes #87 RhodeCode crashes occasionally on updating repository owner
697 - fixes #87 RhodeCode crashes occasionally on updating repository owner
677 - fixes #82 broken annotations on files with more than 1 blank line at the end
698 - fixes #82 broken annotations on files with more than 1 blank line at the end
678 - a lot of fixes and tweaks for file browser
699 - a lot of fixes and tweaks for file browser
679 - fixed detached session issues
700 - fixed detached session issues
680 - fixed when user had no repos he would see all repos listed in my account
701 - fixed when user had no repos he would see all repos listed in my account
681 - fixed ui() instance bug when global hgrc settings was loaded for server
702 - fixed ui() instance bug when global hgrc settings was loaded for server
682 instance and all hgrc options were merged with our db ui() object
703 instance and all hgrc options were merged with our db ui() object
683 - numerous small bugfixes
704 - numerous small bugfixes
684
705
685 (special thanks for TkSoh for detailed feedback)
706 (special thanks for TkSoh for detailed feedback)
686
707
687
708
688 1.0.2 (**2010-11-12**)
709 1.0.2 (**2010-11-12**)
689 ----------------------
710 ----------------------
690
711
691 news
712 news
692 ++++
713 ++++
693
714
694 - tested under python2.7
715 - tested under python2.7
695 - bumped sqlalchemy and celery versions
716 - bumped sqlalchemy and celery versions
696
717
697 fixes
718 fixes
698 +++++
719 +++++
699
720
700 - fixed #59 missing graph.js
721 - fixed #59 missing graph.js
701 - fixed repo_size crash when repository had broken symlinks
722 - fixed repo_size crash when repository had broken symlinks
702 - fixed python2.5 crashes.
723 - fixed python2.5 crashes.
703
724
704
725
705 1.0.1 (**2010-11-10**)
726 1.0.1 (**2010-11-10**)
706 ----------------------
727 ----------------------
707
728
708 news
729 news
709 ++++
730 ++++
710
731
711 - small css updated
732 - small css updated
712
733
713 fixes
734 fixes
714 +++++
735 +++++
715
736
716 - fixed #53 python2.5 incompatible enumerate calls
737 - fixed #53 python2.5 incompatible enumerate calls
717 - fixed #52 disable mercurial extension for web
738 - fixed #52 disable mercurial extension for web
718 - fixed #51 deleting repositories don't delete it's dependent objects
739 - fixed #51 deleting repositories don't delete it's dependent objects
719
740
720
741
721 1.0.0 (**2010-11-02**)
742 1.0.0 (**2010-11-02**)
722 ----------------------
743 ----------------------
723
744
724 - security bugfix simplehg wasn't checking for permissions on commands
745 - security bugfix simplehg wasn't checking for permissions on commands
725 other than pull or push.
746 other than pull or push.
726 - fixed doubled messages after push or pull in admin journal
747 - fixed doubled messages after push or pull in admin journal
727 - templating and css corrections, fixed repo switcher on chrome, updated titles
748 - templating and css corrections, fixed repo switcher on chrome, updated titles
728 - admin menu accessible from options menu on repository view
749 - admin menu accessible from options menu on repository view
729 - permissions cached queries
750 - permissions cached queries
730
751
731 1.0.0rc4 (**2010-10-12**)
752 1.0.0rc4 (**2010-10-12**)
732 --------------------------
753 --------------------------
733
754
734 - fixed python2.5 missing simplejson imports (thanks to Jens Bäckman)
755 - fixed python2.5 missing simplejson imports (thanks to Jens Bäckman)
735 - removed cache_manager settings from sqlalchemy meta
756 - removed cache_manager settings from sqlalchemy meta
736 - added sqlalchemy cache settings to ini files
757 - added sqlalchemy cache settings to ini files
737 - validated password length and added second try of failure on paster setup-app
758 - validated password length and added second try of failure on paster setup-app
738 - fixed setup database destroy prompt even when there was no db
759 - fixed setup database destroy prompt even when there was no db
739
760
740
761
741 1.0.0rc3 (**2010-10-11**)
762 1.0.0rc3 (**2010-10-11**)
742 -------------------------
763 -------------------------
743
764
744 - fixed i18n during installation.
765 - fixed i18n during installation.
745
766
746 1.0.0rc2 (**2010-10-11**)
767 1.0.0rc2 (**2010-10-11**)
747 -------------------------
768 -------------------------
748
769
749 - Disabled dirsize in file browser, it's causing nasty bug when dir renames
770 - Disabled dirsize in file browser, it's causing nasty bug when dir renames
750 occure. After vcs is fixed it'll be put back again.
771 occure. After vcs is fixed it'll be put back again.
751 - templating/css rewrites, optimized css. No newline at end of file
772 - templating/css rewrites, optimized css.
@@ -1,244 +1,250 b''
1 .. _installation_win:
1 .. _installation_win:
2
2
3
3
4 Step by step Installation for Windows
4 Step by step Installation for Windows
5 =====================================
5 =====================================
6
6
7
7
8 RhodeCode step-by-step install Guide for Windows
8 RhodeCode step-by-step install Guide for Windows
9
9
10 Target OS: Windows XP SP3 English (Clean installation)
10 Target OS: Windows XP SP3 32bit English (Clean installation)
11 + All Windows Updates until 24-may-2012
11 + All Windows Updates until 24-may-2012
12
12
13 .. note::
14
15 This installation is for 32bit systems, for 64bit windows you might need
16 to download proper 64bit version of "Windows Installer" and Win32py
17 extensions
18
13 Step1 - Install Visual Studio 2008 Express
19 Step1 - Install Visual Studio 2008 Express
14 ------------------------------------------
20 ------------------------------------------
15
21
16
22
17 Optional: You can also install MingW, but VS2008 installation is easier
23 Optional: You can also install MingW, but VS2008 installation is easier
18
24
19 Download "Visual C++ 2008 Express Edition with SP1" from:
25 Download "Visual C++ 2008 Express Edition with SP1" from:
20 http://www.microsoft.com/visualstudio/en-us/products/2008-editions/express
26 http://www.microsoft.com/visualstudio/en-us/products/2008-editions/express
21 (if not found or relocated, google for "visual studio 2008 express" for
27 (if not found or relocated, google for "visual studio 2008 express" for
22 updated link)
28 updated link)
23
29
24 You can also download full ISO file for offline installation, just
30 You can also download full ISO file for offline installation, just
25 choose "All - Offline Install ISO image file" in the previous page and
31 choose "All - Offline Install ISO image file" in the previous page and
26 choose "Visual C++ 2008 Express" when installing.
32 choose "Visual C++ 2008 Express" when installing.
27
33
28
34
29 .. note::
35 .. note::
30
36
31 Silverlight Runtime and SQL Server 2008 Express Edition are not
37 Silverlight Runtime and SQL Server 2008 Express Edition are not
32 required, you can uncheck them
38 required, you can uncheck them
33
39
34
40
35 Step2 - Install Python
41 Step2 - Install Python
36 ----------------------
42 ----------------------
37
43
38 Install Python 2.x.y (x >= 5) x86 version (32bit). DO NOT USE A 3.x version.
44 Install Python 2.x.y (x >= 5) x86 version (32bit). DO NOT USE A 3.x version.
39 Download Python 2.x.y from:
45 Download Python 2.x.y from:
40 http://www.python.org/download/
46 http://www.python.org/download/
41
47
42 Choose "Windows Installer" (32bit version) not "Windows X86-64
48 Choose "Windows Installer" (32bit version) not "Windows X86-64
43 Installer". While writing this guide, the latest version was v2.7.3.
49 Installer". While writing this guide, the latest version was v2.7.3.
44 Remember the specific major and minor version installed, because it will
50 Remember the specific major and minor version installed, because it will
45 be needed in the next step. In this case, it is "2.7".
51 be needed in the next step. In this case, it is "2.7".
46
52
47
53
48 Step3 - Install Win32py extensions
54 Step3 - Install Win32py extensions
49 ----------------------------------
55 ----------------------------------
50
56
51 Download pywin32 from:
57 Download pywin32 from:
52 http://sourceforge.net/projects/pywin32/files/
58 http://sourceforge.net/projects/pywin32/files/
53
59
54 - Click on "pywin32" folder
60 - Click on "pywin32" folder
55 - Click on the first folder (in this case, Build 217, maybe newer when you try)
61 - Click on the first folder (in this case, Build 217, maybe newer when you try)
56 - Choose the file ending with ".win32-py2.x.exe" -> x being the minor
62 - Choose the file ending with ".win32-py2.x.exe" -> x being the minor
57 version of Python you installed (in this case, 7)
63 version of Python you installed (in this case, 7)
58 When writing this guide, the file was:
64 When writing this guide, the file was:
59 http://sourceforge.net/projects/pywin32/files/pywin32/Build%20217/pywin32-217.win32-py2.7.exe/download
65 http://sourceforge.net/projects/pywin32/files/pywin32/Build%20217/pywin32-217.win32-py2.7.exe/download
60
66
61
67
62 Step4 - Python BIN
68 Step4 - Python BIN
63 ------------------
69 ------------------
64
70
65 Add Python BIN folder to the path
71 Add Python BIN folder to the path
66
72
67 You have to add the Python folder to the path, you can do it manually
73 You have to add the Python folder to the path, you can do it manually
68 (editing "PATH" environment variable) or using Windows Support Tools
74 (editing "PATH" environment variable) or using Windows Support Tools
69 that came preinstalled in Vista/7 and can be installed in Windows XP.
75 that came preinstalled in Vista/7 and can be installed in Windows XP.
70
76
71 - Using support tools on WINDOWS XP:
77 - Using support tools on WINDOWS XP:
72 If you use Windows XP you can install them using Windows XP CD and
78 If you use Windows XP you can install them using Windows XP CD and
73 navigating to \SUPPORT\TOOLS. There, execute Setup.EXE (not MSI).
79 navigating to \SUPPORT\TOOLS. There, execute Setup.EXE (not MSI).
74 Afterwards, open a CMD and type::
80 Afterwards, open a CMD and type::
75
81
76 SETX PATH "%PATH%;[your-python-path]" -M
82 SETX PATH "%PATH%;[your-python-path]" -M
77
83
78 Close CMD (the path variable will be updated then)
84 Close CMD (the path variable will be updated then)
79
85
80 - Using support tools on WINDOWS Vista/7:
86 - Using support tools on WINDOWS Vista/7:
81
87
82 Open a CMD and type::
88 Open a CMD and type::
83
89
84 SETX PATH "%PATH%;[your-python-path]" /M
90 SETX PATH "%PATH%;[your-python-path]" /M
85
91
86 Please substitute [your-python-path] with your Python installation path.
92 Please substitute [your-python-path] with your Python installation path.
87 Typically: C:\\Python27
93 Typically: C:\\Python27
88
94
89
95
90 Step5 - RhodeCode folder structure
96 Step5 - RhodeCode folder structure
91 ----------------------------------
97 ----------------------------------
92
98
93 Create a RhodeCode folder structure
99 Create a RhodeCode folder structure
94
100
95 This is only a example to install RhodeCode, you can of course change
101 This is only a example to install RhodeCode, you can of course change
96 it. However, this guide will follow the proposed structure, so please
102 it. However, this guide will follow the proposed structure, so please
97 later adapt the paths if you change them. My recommendation is to use
103 later adapt the paths if you change them. My recommendation is to use
98 folders with NO SPACES. But you can try if you are brave...
104 folders with NO SPACES. But you can try if you are brave...
99
105
100 Create the following folder structure::
106 Create the following folder structure::
101
107
102 C:\RhodeCode
108 C:\RhodeCode
103 C:\RhodeCode\Bin
109 C:\RhodeCode\Bin
104 C:\RhodeCode\Env
110 C:\RhodeCode\Env
105 C:\RhodeCode\Repos
111 C:\RhodeCode\Repos
106
112
107
113
108 Step6 - Install virtualenv
114 Step6 - Install virtualenv
109 ---------------------------
115 ---------------------------
110
116
111 Install Virtual Env for Python
117 Install Virtual Env for Python
112
118
113 Navigate to: http://www.virtualenv.org/en/latest/index.html#installation
119 Navigate to: http://www.virtualenv.org/en/latest/index.html#installation
114 Right click on "virtualenv.py" file and choose "Save link as...".
120 Right click on "virtualenv.py" file and choose "Save link as...".
115 Download to C:\\RhodeCode (or whatever you want)
121 Download to C:\\RhodeCode (or whatever you want)
116 (the file is located at
122 (the file is located at
117 https://raw.github.com/pypa/virtualenv/master/virtualenv.py)
123 https://raw.github.com/pypa/virtualenv/master/virtualenv.py)
118
124
119 Create a virtual Python environment in C:\\RhodeCode\\Env (or similar). To
125 Create a virtual Python environment in C:\\RhodeCode\\Env (or similar). To
120 do so, open a CMD (Python Path should be included in Step3), navigate
126 do so, open a CMD (Python Path should be included in Step3), navigate
121 where you downloaded "virtualenv.py", and write::
127 where you downloaded "virtualenv.py", and write::
122
128
123 python virtualenv.py C:\RhodeCode\Env
129 python virtualenv.py C:\RhodeCode\Env
124
130
125 (--no-site-packages is now the default behaviour of virtualenv, no need
131 (--no-site-packages is now the default behaviour of virtualenv, no need
126 to include it)
132 to include it)
127
133
128
134
129 Step7 - Install RhodeCode
135 Step7 - Install RhodeCode
130 -------------------------
136 -------------------------
131
137
132 Finally, install RhodeCode
138 Finally, install RhodeCode
133
139
134 Close previously opened command prompt/s, and open a Visual Studio 2008
140 Close previously opened command prompt/s, and open a Visual Studio 2008
135 Command Prompt (**IMPORTANT!!**). To do so, go to Start Menu, and then open
141 Command Prompt (**IMPORTANT!!**). To do so, go to Start Menu, and then open
136 "Microsoft Visual C++ 2008 Express Edition" -> "Visual Studio Tools" ->
142 "Microsoft Visual C++ 2008 Express Edition" -> "Visual Studio Tools" ->
137 "Visual Studio 2008 Command Prompt"
143 "Visual Studio 2008 Command Prompt"
138
144
139 In that CMD (loaded with VS2008 PATHs) type::
145 In that CMD (loaded with VS2008 PATHs) type::
140
146
141 cd C:\RhodeCode\Env\Scripts (or similar)
147 cd C:\RhodeCode\Env\Scripts (or similar)
142 activate
148 activate
143
149
144 The prompt will change into "(Env) C:\\RhodeCode\\Env\\Scripts" or similar
150 The prompt will change into "(Env) C:\\RhodeCode\\Env\\Scripts" or similar
145 (depending of your folder structure). Then type::
151 (depending of your folder structure). Then type::
146
152
147 pip install rhodecode
153 pip install rhodecode
148
154
149 (long step, please wait until fully complete)
155 (long step, please wait until fully complete)
150
156
151 Some warnings will appear, don't worry as they are normal.
157 Some warnings will appear, don't worry as they are normal.
152
158
153
159
154 Step8 - Configuring RhodeCode
160 Step8 - Configuring RhodeCode
155 -----------------------------
161 -----------------------------
156
162
157
163
158 steps taken from http://packages.python.org/RhodeCode/setup.html
164 steps taken from http://packages.python.org/RhodeCode/setup.html
159
165
160 You have to use the same Visual Studio 2008 command prompt as Step7, so
166 You have to use the same Visual Studio 2008 command prompt as Step7, so
161 if you closed it reopen it following the same commands (including the
167 if you closed it reopen it following the same commands (including the
162 "activate" one). When ready, just type::
168 "activate" one). When ready, just type::
163
169
164 cd C:\RhodeCode\Bin
170 cd C:\RhodeCode\Bin
165 paster make-config RhodeCode production.ini
171 paster make-config RhodeCode production.ini
166
172
167 Then, you must edit production.ini to fit your needs (ip address, ip
173 Then, you must edit production.ini to fit your needs (ip address, ip
168 port, mail settings, database, whatever). I recommend using NotePad++
174 port, mail settings, database, whatever). I recommend using NotePad++
169 (free) or similar text editor, as it handles well the EndOfLine
175 (free) or similar text editor, as it handles well the EndOfLine
170 character differences between Unix and Windows
176 character differences between Unix and Windows
171 (http://notepad-plus-plus.org/)
177 (http://notepad-plus-plus.org/)
172
178
173 For the sake of simplicity lets run it with the default settings. After
179 For the sake of simplicity lets run it with the default settings. After
174 your edits (if any), in the previous Command Prompt, type::
180 your edits (if any), in the previous Command Prompt, type::
175
181
176 paster setup-rhodecode production.ini
182 paster setup-rhodecode production.ini
177
183
178 (this time a NEW database will be installed, you must follow a different
184 (this time a NEW database will be installed, you must follow a different
179 step to later UPGRADE to a newer RhodeCode version)
185 step to later UPGRADE to a newer RhodeCode version)
180
186
181 The script will ask you for confirmation about creating a NEW database,
187 The script will ask you for confirmation about creating a NEW database,
182 answer yes (y)
188 answer yes (y)
183 The script will ask you for repository path, answer C:\\RhodeCode\\Repos
189 The script will ask you for repository path, answer C:\\RhodeCode\\Repos
184 (or similar)
190 (or similar)
185 The script will ask you for admin username and password, answer "admin"
191 The script will ask you for admin username and password, answer "admin"
186 + "123456" (or whatever you want)
192 + "123456" (or whatever you want)
187 The script will ask you for admin mail, answer "admin@xxxx.com" (or
193 The script will ask you for admin mail, answer "admin@xxxx.com" (or
188 whatever you want)
194 whatever you want)
189
195
190 If you make some mistake and the script does not end, don't worry, start
196 If you make some mistake and the script does not end, don't worry, start
191 it again.
197 it again.
192
198
193
199
194 Step9 - Running RhodeCode
200 Step9 - Running RhodeCode
195 -------------------------
201 -------------------------
196
202
197
203
198 In the previous command prompt, being in the C:\\RhodeCode\\Bin folder,
204 In the previous command prompt, being in the C:\\RhodeCode\\Bin folder,
199 just type::
205 just type::
200
206
201 paster serve production.ini
207 paster serve production.ini
202
208
203 Open yout web server, and go to http://127.0.0.1:5000
209 Open yout web server, and go to http://127.0.0.1:5000
204
210
205 It works!! :-)
211 It works!! :-)
206
212
207 Remark:
213 Remark:
208 If it does not work first time, just Ctrl-C the CMD process and start it
214 If it does not work first time, just Ctrl-C the CMD process and start it
209 again. Don't forget the "http://" in Internet Explorer
215 again. Don't forget the "http://" in Internet Explorer
210
216
211
217
212
218
213 What this Guide does not cover:
219 What this Guide does not cover:
214
220
215 - Installing Celery
221 - Installing Celery
216 - Running RhodeCode as Windows Service. You can investigate here:
222 - Running RhodeCode as Windows Service. You can investigate here:
217
223
218 - http://pypi.python.org/pypi/wsgisvc
224 - http://pypi.python.org/pypi/wsgisvc
219 - http://ryrobes.com/python/running-python-scripts-as-a-windows-service/
225 - http://ryrobes.com/python/running-python-scripts-as-a-windows-service/
220 - http://wiki.pylonshq.com/display/pylonscookbook/How+to+run+Pylons+as+a+Windows+service
226 - http://wiki.pylonshq.com/display/pylonscookbook/How+to+run+Pylons+as+a+Windows+service
221
227
222 - Using Apache. You can investigate here:
228 - Using Apache. You can investigate here:
223
229
224 - https://groups.google.com/group/rhodecode/msg/c433074e813ffdc4
230 - https://groups.google.com/group/rhodecode/msg/c433074e813ffdc4
225
231
226
232
227 Upgrading
233 Upgrading
228 =========
234 =========
229
235
230 Stop running RhodeCode
236 Stop running RhodeCode
231 Open a CommandPrompt like in Step7 (VS2008 path + activate) and type::
237 Open a CommandPrompt like in Step7 (VS2008 path + activate) and type::
232
238
233 easy_install -U rhodecode
239 easy_install -U rhodecode
234 cd \RhodeCode\Bin
240 cd \RhodeCode\Bin
235
241
236 { backup your production.ini file now} ::
242 { backup your production.ini file now} ::
237
243
238 paster make-config RhodeCode production.ini
244 paster make-config RhodeCode production.ini
239
245
240 (check changes and update your production.ini accordingly) ::
246 (check changes and update your production.ini accordingly) ::
241
247
242 paster upgrade-db production.ini (update database)
248 paster upgrade-db production.ini (update database)
243
249
244 Full steps in http://packages.python.org/RhodeCode/upgrade.html No newline at end of file
250 Full steps in http://packages.python.org/RhodeCode/upgrade.html
@@ -1,329 +1,331 b''
1 ################################################################################
1 ################################################################################
2 ################################################################################
2 ################################################################################
3 # RhodeCode - Pylons environment configuration #
3 # RhodeCode - Pylons environment configuration #
4 # #
4 # #
5 # The %(here)s variable will be replaced with the parent directory of this file#
5 # The %(here)s variable will be replaced with the parent directory of this file#
6 ################################################################################
6 ################################################################################
7
7
8 [DEFAULT]
8 [DEFAULT]
9 debug = true
9 debug = true
10 pdebug = false
10 pdebug = false
11 ################################################################################
11 ################################################################################
12 ## Uncomment and replace with the address which should receive ##
12 ## Uncomment and replace with the address which should receive ##
13 ## any error reports after application crash ##
13 ## any error reports after application crash ##
14 ## Additionally those settings will be used by RhodeCode mailing system ##
14 ## Additionally those settings will be used by RhodeCode mailing system ##
15 ################################################################################
15 ################################################################################
16 #email_to = admin@localhost
16 #email_to = admin@localhost
17 #error_email_from = paste_error@localhost
17 #error_email_from = paste_error@localhost
18 #app_email_from = rhodecode-noreply@localhost
18 #app_email_from = rhodecode-noreply@localhost
19 #error_message =
19 #error_message =
20 #email_prefix = [RhodeCode]
20 #email_prefix = [RhodeCode]
21
21
22 #smtp_server = mail.server.com
22 #smtp_server = mail.server.com
23 #smtp_username =
23 #smtp_username =
24 #smtp_password =
24 #smtp_password =
25 #smtp_port =
25 #smtp_port =
26 #smtp_use_tls = false
26 #smtp_use_tls = false
27 #smtp_use_ssl = true
27 #smtp_use_ssl = true
28 # Specify available auth parameters here (e.g. LOGIN PLAIN CRAM-MD5, etc.)
28 # Specify available auth parameters here (e.g. LOGIN PLAIN CRAM-MD5, etc.)
29 #smtp_auth =
29 #smtp_auth =
30
30
31 [server:main]
31 [server:main]
32 ##nr of threads to spawn
32 ##nr of threads to spawn
33 #threadpool_workers = 5
33 #threadpool_workers = 5
34
34
35 ##max request before thread respawn
35 ##max request before thread respawn
36 #threadpool_max_requests = 10
36 #threadpool_max_requests = 10
37
37
38 ##option to use threads of process
38 ##option to use threads of process
39 #use_threadpool = true
39 #use_threadpool = true
40
40
41 #use = egg:Paste#http
41 #use = egg:Paste#http
42 use = egg:waitress#main
42 use = egg:waitress#main
43 host = 127.0.0.1
43 host = 127.0.0.1
44 port = 8001
44 port = 8001
45
45
46 [filter:proxy-prefix]
46 [filter:proxy-prefix]
47 # prefix middleware for rc
47 # prefix middleware for rc
48 use = egg:PasteDeploy#prefix
48 use = egg:PasteDeploy#prefix
49 prefix = /<your-prefix>
49 prefix = /<your-prefix>
50
50
51 [app:main]
51 [app:main]
52 use = egg:rhodecode
52 use = egg:rhodecode
53 #filter-with = proxy-prefix
53 #filter-with = proxy-prefix
54 full_stack = true
54 full_stack = true
55 static_files = true
55 static_files = true
56 # Optional Languages
56 # Optional Languages
57 # en, fr, ja, pt_BR, zh_CN, zh_TW
57 # en, fr, ja, pt_BR, zh_CN, zh_TW
58 lang = en
58 lang = en
59 cache_dir = %(here)s/data
59 cache_dir = %(here)s/data
60 index_dir = %(here)s/data/index
60 index_dir = %(here)s/data/index
61 app_instance_uuid = rc-production
61 app_instance_uuid = rc-production
62 cut_off_limit = 256000
62 cut_off_limit = 256000
63 force_https = false
63 force_https = false
64 commit_parse_limit = 50
64 commit_parse_limit = 50
65 use_gravatar = true
65 use_gravatar = true
66
66
67 ## alternative_gravatar_url allows you to use your own avatar server application
67 ## alternative_gravatar_url allows you to use your own avatar server application
68 ## the following parts of the URL will be replaced
68 ## the following parts of the URL will be replaced
69 ## {email} user email
69 ## {email} user email
70 ## {md5email} md5 hash of the user email (like at gravatar.com)
70 ## {md5email} md5 hash of the user email (like at gravatar.com)
71 ## {size} size of the image that is expected from the server application
71 ## {size} size of the image that is expected from the server application
72 ## {scheme} http/https from RhodeCode server
73 ## {netloc} network location from RhodeCode server
72 #alternative_gravatar_url = http://myavatarserver.com/getbyemail/{email}/{size}
74 #alternative_gravatar_url = http://myavatarserver.com/getbyemail/{email}/{size}
73 #alternative_gravatar_url = http://myavatarserver.com/getbymd5/{md5email}?s={size}
75 #alternative_gravatar_url = http://myavatarserver.com/getbymd5/{md5email}?s={size}
74
76
75 container_auth_enabled = false
77 container_auth_enabled = false
76 proxypass_auth_enabled = false
78 proxypass_auth_enabled = false
77 default_encoding = utf8
79 default_encoding = utf8
78
80
79 ## overwrite schema of clone url
81 ## overwrite schema of clone url
80 ## available vars:
82 ## available vars:
81 ## scheme - http/https
83 ## scheme - http/https
82 ## user - current user
84 ## user - current user
83 ## pass - password
85 ## pass - password
84 ## netloc - network location
86 ## netloc - network location
85 ## path - usually repo_name
87 ## path - usually repo_name
86
88
87 #clone_uri = {scheme}://{user}{pass}{netloc}{path}
89 #clone_uri = {scheme}://{user}{pass}{netloc}{path}
88
90
89 ## issue tracking mapping for commits messages
91 ## issue tracking mapping for commits messages
90 ## comment out issue_pat, issue_server, issue_prefix to enable
92 ## comment out issue_pat, issue_server, issue_prefix to enable
91
93
92 ## pattern to get the issues from commit messages
94 ## pattern to get the issues from commit messages
93 ## default one used here is #<numbers> with a regex passive group for `#`
95 ## default one used here is #<numbers> with a regex passive group for `#`
94 ## {id} will be all groups matched from this pattern
96 ## {id} will be all groups matched from this pattern
95
97
96 issue_pat = (?:\s*#)(\d+)
98 issue_pat = (?:\s*#)(\d+)
97
99
98 ## server url to the issue, each {id} will be replaced with match
100 ## server url to the issue, each {id} will be replaced with match
99 ## fetched from the regex and {repo} is replaced with full repository name
101 ## fetched from the regex and {repo} is replaced with full repository name
100 ## including groups {repo_name} is replaced with just name of repo
102 ## including groups {repo_name} is replaced with just name of repo
101
103
102 issue_server_link = https://myissueserver.com/{repo}/issue/{id}
104 issue_server_link = https://myissueserver.com/{repo}/issue/{id}
103
105
104 ## prefix to add to link to indicate it's an url
106 ## prefix to add to link to indicate it's an url
105 ## #314 will be replaced by <issue_prefix><id>
107 ## #314 will be replaced by <issue_prefix><id>
106
108
107 issue_prefix = #
109 issue_prefix = #
108
110
109 ## instance-id prefix
111 ## instance-id prefix
110 ## a prefix key for this instance used for cache invalidation when running
112 ## a prefix key for this instance used for cache invalidation when running
111 ## multiple instances of rhodecode, make sure it's globally unique for
113 ## multiple instances of rhodecode, make sure it's globally unique for
112 ## all running rhodecode instances. Leave empty if you don't use it
114 ## all running rhodecode instances. Leave empty if you don't use it
113 instance_id =
115 instance_id =
114
116
115 ## alternative return HTTP header for failed authentication. Default HTTP
117 ## alternative return HTTP header for failed authentication. Default HTTP
116 ## response is 401 HTTPUnauthorized. Currently HG clients have troubles with
118 ## response is 401 HTTPUnauthorized. Currently HG clients have troubles with
117 ## handling that. Set this variable to 403 to return HTTPForbidden
119 ## handling that. Set this variable to 403 to return HTTPForbidden
118 auth_ret_code =
120 auth_ret_code =
119
121
120 ####################################
122 ####################################
121 ### CELERY CONFIG ####
123 ### CELERY CONFIG ####
122 ####################################
124 ####################################
123 use_celery = false
125 use_celery = false
124 broker.host = localhost
126 broker.host = localhost
125 broker.vhost = rabbitmqhost
127 broker.vhost = rabbitmqhost
126 broker.port = 5672
128 broker.port = 5672
127 broker.user = rabbitmq
129 broker.user = rabbitmq
128 broker.password = qweqwe
130 broker.password = qweqwe
129
131
130 celery.imports = rhodecode.lib.celerylib.tasks
132 celery.imports = rhodecode.lib.celerylib.tasks
131
133
132 celery.result.backend = amqp
134 celery.result.backend = amqp
133 celery.result.dburi = amqp://
135 celery.result.dburi = amqp://
134 celery.result.serialier = json
136 celery.result.serialier = json
135
137
136 #celery.send.task.error.emails = true
138 #celery.send.task.error.emails = true
137 #celery.amqp.task.result.expires = 18000
139 #celery.amqp.task.result.expires = 18000
138
140
139 celeryd.concurrency = 2
141 celeryd.concurrency = 2
140 #celeryd.log.file = celeryd.log
142 #celeryd.log.file = celeryd.log
141 celeryd.log.level = debug
143 celeryd.log.level = debug
142 celeryd.max.tasks.per.child = 1
144 celeryd.max.tasks.per.child = 1
143
145
144 #tasks will never be sent to the queue, but executed locally instead.
146 #tasks will never be sent to the queue, but executed locally instead.
145 celery.always.eager = false
147 celery.always.eager = false
146
148
147 ####################################
149 ####################################
148 ### BEAKER CACHE ####
150 ### BEAKER CACHE ####
149 ####################################
151 ####################################
150 beaker.cache.data_dir=%(here)s/data/cache/data
152 beaker.cache.data_dir=%(here)s/data/cache/data
151 beaker.cache.lock_dir=%(here)s/data/cache/lock
153 beaker.cache.lock_dir=%(here)s/data/cache/lock
152
154
153 beaker.cache.regions=super_short_term,short_term,long_term,sql_cache_short,sql_cache_med,sql_cache_long
155 beaker.cache.regions=super_short_term,short_term,long_term,sql_cache_short,sql_cache_med,sql_cache_long
154
156
155 beaker.cache.super_short_term.type=memory
157 beaker.cache.super_short_term.type=memory
156 beaker.cache.super_short_term.expire=10
158 beaker.cache.super_short_term.expire=10
157 beaker.cache.super_short_term.key_length = 256
159 beaker.cache.super_short_term.key_length = 256
158
160
159 beaker.cache.short_term.type=memory
161 beaker.cache.short_term.type=memory
160 beaker.cache.short_term.expire=60
162 beaker.cache.short_term.expire=60
161 beaker.cache.short_term.key_length = 256
163 beaker.cache.short_term.key_length = 256
162
164
163 beaker.cache.long_term.type=memory
165 beaker.cache.long_term.type=memory
164 beaker.cache.long_term.expire=36000
166 beaker.cache.long_term.expire=36000
165 beaker.cache.long_term.key_length = 256
167 beaker.cache.long_term.key_length = 256
166
168
167 beaker.cache.sql_cache_short.type=memory
169 beaker.cache.sql_cache_short.type=memory
168 beaker.cache.sql_cache_short.expire=10
170 beaker.cache.sql_cache_short.expire=10
169 beaker.cache.sql_cache_short.key_length = 256
171 beaker.cache.sql_cache_short.key_length = 256
170
172
171 beaker.cache.sql_cache_med.type=memory
173 beaker.cache.sql_cache_med.type=memory
172 beaker.cache.sql_cache_med.expire=360
174 beaker.cache.sql_cache_med.expire=360
173 beaker.cache.sql_cache_med.key_length = 256
175 beaker.cache.sql_cache_med.key_length = 256
174
176
175 beaker.cache.sql_cache_long.type=file
177 beaker.cache.sql_cache_long.type=file
176 beaker.cache.sql_cache_long.expire=3600
178 beaker.cache.sql_cache_long.expire=3600
177 beaker.cache.sql_cache_long.key_length = 256
179 beaker.cache.sql_cache_long.key_length = 256
178
180
179 ####################################
181 ####################################
180 ### BEAKER SESSION ####
182 ### BEAKER SESSION ####
181 ####################################
183 ####################################
182 ## Type of storage used for the session, current types are
184 ## Type of storage used for the session, current types are
183 ## dbm, file, memcached, database, and memory.
185 ## dbm, file, memcached, database, and memory.
184 ## The storage uses the Container API
186 ## The storage uses the Container API
185 ## that is also used by the cache system.
187 ## that is also used by the cache system.
186
188
187 ## db session ##
189 ## db session ##
188 #beaker.session.type = ext:database
190 #beaker.session.type = ext:database
189 #beaker.session.sa.url = postgresql://postgres:qwe@localhost/rhodecode
191 #beaker.session.sa.url = postgresql://postgres:qwe@localhost/rhodecode
190 #beaker.session.table_name = db_session
192 #beaker.session.table_name = db_session
191
193
192 ## encrypted cookie client side session, good for many instances ##
194 ## encrypted cookie client side session, good for many instances ##
193 #beaker.session.type = cookie
195 #beaker.session.type = cookie
194
196
195 ## file based cookies (default) ##
197 ## file based cookies (default) ##
196 #beaker.session.type = file
198 #beaker.session.type = file
197
199
198
200
199 beaker.session.key = rhodecode
201 beaker.session.key = rhodecode
200 ## secure cookie requires AES python libraries ##
202 ## secure cookie requires AES python libraries ##
201 #beaker.session.encrypt_key = g654dcno0-9873jhgfreyu
203 #beaker.session.encrypt_key = g654dcno0-9873jhgfreyu
202 #beaker.session.validate_key = 9712sds2212c--zxc123
204 #beaker.session.validate_key = 9712sds2212c--zxc123
203 ## sets session as invalid if it haven't been accessed for given amount of time
205 ## sets session as invalid if it haven't been accessed for given amount of time
204 beaker.session.timeout = 2592000
206 beaker.session.timeout = 2592000
205 beaker.session.httponly = true
207 beaker.session.httponly = true
206 #beaker.session.cookie_path = /<your-prefix>
208 #beaker.session.cookie_path = /<your-prefix>
207
209
208 ## uncomment for https secure cookie ##
210 ## uncomment for https secure cookie ##
209 beaker.session.secure = false
211 beaker.session.secure = false
210
212
211 ## auto save the session to not to use .save() ##
213 ## auto save the session to not to use .save() ##
212 beaker.session.auto = False
214 beaker.session.auto = False
213
215
214 ## default cookie expiration time in seconds `true` expire at browser close ##
216 ## default cookie expiration time in seconds `true` expire at browser close ##
215 #beaker.session.cookie_expires = 3600
217 #beaker.session.cookie_expires = 3600
216
218
217
219
218 ################################################################################
220 ################################################################################
219 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
221 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
220 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
222 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
221 ## execute malicious code after an exception is raised. ##
223 ## execute malicious code after an exception is raised. ##
222 ################################################################################
224 ################################################################################
223 set debug = false
225 set debug = false
224
226
225 ##################################
227 ##################################
226 ### LOGVIEW CONFIG ###
228 ### LOGVIEW CONFIG ###
227 ##################################
229 ##################################
228 logview.sqlalchemy = #faa
230 logview.sqlalchemy = #faa
229 logview.pylons.templating = #bfb
231 logview.pylons.templating = #bfb
230 logview.pylons.util = #eee
232 logview.pylons.util = #eee
231
233
232 #########################################################
234 #########################################################
233 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
235 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
234 #########################################################
236 #########################################################
235 #sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db
237 #sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db
236 sqlalchemy.db1.url = postgresql://postgres:qwe@localhost/rhodecode
238 sqlalchemy.db1.url = postgresql://postgres:qwe@localhost/rhodecode
237 sqlalchemy.db1.echo = false
239 sqlalchemy.db1.echo = false
238 sqlalchemy.db1.pool_recycle = 3600
240 sqlalchemy.db1.pool_recycle = 3600
239 sqlalchemy.db1.convert_unicode = true
241 sqlalchemy.db1.convert_unicode = true
240
242
241 ################################
243 ################################
242 ### LOGGING CONFIGURATION ####
244 ### LOGGING CONFIGURATION ####
243 ################################
245 ################################
244 [loggers]
246 [loggers]
245 keys = root, routes, rhodecode, sqlalchemy, beaker, templates, whoosh_indexer
247 keys = root, routes, rhodecode, sqlalchemy, beaker, templates, whoosh_indexer
246
248
247 [handlers]
249 [handlers]
248 keys = console, console_sql
250 keys = console, console_sql
249
251
250 [formatters]
252 [formatters]
251 keys = generic, color_formatter, color_formatter_sql
253 keys = generic, color_formatter, color_formatter_sql
252
254
253 #############
255 #############
254 ## LOGGERS ##
256 ## LOGGERS ##
255 #############
257 #############
256 [logger_root]
258 [logger_root]
257 level = NOTSET
259 level = NOTSET
258 handlers = console
260 handlers = console
259
261
260 [logger_routes]
262 [logger_routes]
261 level = DEBUG
263 level = DEBUG
262 handlers =
264 handlers =
263 qualname = routes.middleware
265 qualname = routes.middleware
264 # "level = DEBUG" logs the route matched and routing variables.
266 # "level = DEBUG" logs the route matched and routing variables.
265 propagate = 1
267 propagate = 1
266
268
267 [logger_beaker]
269 [logger_beaker]
268 level = DEBUG
270 level = DEBUG
269 handlers =
271 handlers =
270 qualname = beaker.container
272 qualname = beaker.container
271 propagate = 1
273 propagate = 1
272
274
273 [logger_templates]
275 [logger_templates]
274 level = INFO
276 level = INFO
275 handlers =
277 handlers =
276 qualname = pylons.templating
278 qualname = pylons.templating
277 propagate = 1
279 propagate = 1
278
280
279 [logger_rhodecode]
281 [logger_rhodecode]
280 level = DEBUG
282 level = DEBUG
281 handlers =
283 handlers =
282 qualname = rhodecode
284 qualname = rhodecode
283 propagate = 1
285 propagate = 1
284
286
285 [logger_sqlalchemy]
287 [logger_sqlalchemy]
286 level = INFO
288 level = INFO
287 handlers = console_sql
289 handlers = console_sql
288 qualname = sqlalchemy.engine
290 qualname = sqlalchemy.engine
289 propagate = 0
291 propagate = 0
290
292
291 [logger_whoosh_indexer]
293 [logger_whoosh_indexer]
292 level = DEBUG
294 level = DEBUG
293 handlers =
295 handlers =
294 qualname = whoosh_indexer
296 qualname = whoosh_indexer
295 propagate = 1
297 propagate = 1
296
298
297 ##############
299 ##############
298 ## HANDLERS ##
300 ## HANDLERS ##
299 ##############
301 ##############
300
302
301 [handler_console]
303 [handler_console]
302 class = StreamHandler
304 class = StreamHandler
303 args = (sys.stderr,)
305 args = (sys.stderr,)
304 level = INFO
306 level = INFO
305 formatter = generic
307 formatter = generic
306
308
307 [handler_console_sql]
309 [handler_console_sql]
308 class = StreamHandler
310 class = StreamHandler
309 args = (sys.stderr,)
311 args = (sys.stderr,)
310 level = WARN
312 level = WARN
311 formatter = generic
313 formatter = generic
312
314
313 ################
315 ################
314 ## FORMATTERS ##
316 ## FORMATTERS ##
315 ################
317 ################
316
318
317 [formatter_generic]
319 [formatter_generic]
318 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
320 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
319 datefmt = %Y-%m-%d %H:%M:%S
321 datefmt = %Y-%m-%d %H:%M:%S
320
322
321 [formatter_color_formatter]
323 [formatter_color_formatter]
322 class=rhodecode.lib.colored_formatter.ColorFormatter
324 class=rhodecode.lib.colored_formatter.ColorFormatter
323 format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
325 format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
324 datefmt = %Y-%m-%d %H:%M:%S
326 datefmt = %Y-%m-%d %H:%M:%S
325
327
326 [formatter_color_formatter_sql]
328 [formatter_color_formatter_sql]
327 class=rhodecode.lib.colored_formatter.ColorFormatterSql
329 class=rhodecode.lib.colored_formatter.ColorFormatterSql
328 format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
330 format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
329 datefmt = %Y-%m-%d %H:%M:%S
331 datefmt = %Y-%m-%d %H:%M:%S
@@ -1,67 +1,67 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.__init__
3 rhodecode.__init__
4 ~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~
5
5
6 RhodeCode, a web based repository management based on pylons
6 RhodeCode, a web based repository management based on pylons
7 versioning implementation: http://www.python.org/dev/peps/pep-0386/
7 versioning implementation: http://www.python.org/dev/peps/pep-0386/
8
8
9 :created_on: Apr 9, 2010
9 :created_on: Apr 9, 2010
10 :author: marcink
10 :author: marcink
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2010-2012 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 sys
26 import sys
27 import platform
27 import platform
28
28
29 VERSION = (1, 4, 1)
29 VERSION = (1, 4, 2)
30
30
31 try:
31 try:
32 from rhodecode.lib import get_current_revision
32 from rhodecode.lib import get_current_revision
33 _rev = get_current_revision(quiet=True)
33 _rev = get_current_revision(quiet=True)
34 if _rev and len(VERSION) > 3:
34 if _rev and len(VERSION) > 3:
35 VERSION += ('dev%s' % _rev[0],)
35 VERSION += ('dev%s' % _rev[0],)
36 except ImportError:
36 except ImportError:
37 pass
37 pass
38
38
39 __version__ = ('.'.join((str(each) for each in VERSION[:3])) +
39 __version__ = ('.'.join((str(each) for each in VERSION[:3])) +
40 '.'.join(VERSION[3:]))
40 '.'.join(VERSION[3:]))
41 __dbversion__ = 7 # defines current db version for migrations
41 __dbversion__ = 7 # defines current db version for migrations
42 __platform__ = platform.system()
42 __platform__ = platform.system()
43 __license__ = 'GPLv3'
43 __license__ = 'GPLv3'
44 __py_version__ = sys.version_info
44 __py_version__ = sys.version_info
45 __author__ = 'Marcin Kuzminski'
45 __author__ = 'Marcin Kuzminski'
46 __url__ = 'http://rhodecode.org'
46 __url__ = 'http://rhodecode.org'
47
47
48 PLATFORM_WIN = ('Windows')
48 PLATFORM_WIN = ('Windows')
49 PLATFORM_OTHERS = ('Linux', 'Darwin', 'FreeBSD', 'OpenBSD', 'SunOS') #depracated
49 PLATFORM_OTHERS = ('Linux', 'Darwin', 'FreeBSD', 'OpenBSD', 'SunOS') #depracated
50
50
51 is_windows = __platform__ in PLATFORM_WIN
51 is_windows = __platform__ in PLATFORM_WIN
52 is_unix = not is_windows
52 is_unix = not is_windows
53
53
54
54
55 BACKENDS = {
55 BACKENDS = {
56 'hg': 'Mercurial repository',
56 'hg': 'Mercurial repository',
57 'git': 'Git repository',
57 'git': 'Git repository',
58 }
58 }
59
59
60 CELERY_ON = False
60 CELERY_ON = False
61 CELERY_EAGER = False
61 CELERY_EAGER = False
62
62
63 # link to config for pylons
63 # link to config for pylons
64 CONFIG = {}
64 CONFIG = {}
65
65
66 # Linked module for extensions
66 # Linked module for extensions
67 EXTENSIONS = {}
67 EXTENSIONS = {}
@@ -1,339 +1,341 b''
1 ################################################################################
1 ################################################################################
2 ################################################################################
2 ################################################################################
3 # RhodeCode - Pylons environment configuration #
3 # RhodeCode - Pylons environment configuration #
4 # #
4 # #
5 # The %(here)s variable will be replaced with the parent directory of this file#
5 # The %(here)s variable will be replaced with the parent directory of this file#
6 ################################################################################
6 ################################################################################
7
7
8 [DEFAULT]
8 [DEFAULT]
9 debug = true
9 debug = true
10 pdebug = false
10 pdebug = false
11 ################################################################################
11 ################################################################################
12 ## Uncomment and replace with the address which should receive ##
12 ## Uncomment and replace with the address which should receive ##
13 ## any error reports after application crash ##
13 ## any error reports after application crash ##
14 ## Additionally those settings will be used by RhodeCode mailing system ##
14 ## Additionally those settings will be used by RhodeCode mailing system ##
15 ################################################################################
15 ################################################################################
16 #email_to = admin@localhost
16 #email_to = admin@localhost
17 #error_email_from = paste_error@localhost
17 #error_email_from = paste_error@localhost
18 #app_email_from = rhodecode-noreply@localhost
18 #app_email_from = rhodecode-noreply@localhost
19 #error_message =
19 #error_message =
20 #email_prefix = [RhodeCode]
20 #email_prefix = [RhodeCode]
21
21
22 #smtp_server = mail.server.com
22 #smtp_server = mail.server.com
23 #smtp_username =
23 #smtp_username =
24 #smtp_password =
24 #smtp_password =
25 #smtp_port =
25 #smtp_port =
26 #smtp_use_tls = false
26 #smtp_use_tls = false
27 #smtp_use_ssl = true
27 #smtp_use_ssl = true
28 # Specify available auth parameters here (e.g. LOGIN PLAIN CRAM-MD5, etc.)
28 # Specify available auth parameters here (e.g. LOGIN PLAIN CRAM-MD5, etc.)
29 #smtp_auth =
29 #smtp_auth =
30
30
31 [server:main]
31 [server:main]
32 ##nr of threads to spawn
32 ##nr of threads to spawn
33 #threadpool_workers = 5
33 #threadpool_workers = 5
34
34
35 ##max request before thread respawn
35 ##max request before thread respawn
36 #threadpool_max_requests = 10
36 #threadpool_max_requests = 10
37
37
38 ##option to use threads of process
38 ##option to use threads of process
39 #use_threadpool = true
39 #use_threadpool = true
40
40
41 #use = egg:Paste#http
41 #use = egg:Paste#http
42 use = egg:waitress#main
42 use = egg:waitress#main
43 host = 127.0.0.1
43 host = 127.0.0.1
44 port = 5000
44 port = 5000
45
45
46 [filter:proxy-prefix]
46 [filter:proxy-prefix]
47 # prefix middleware for rc
47 # prefix middleware for rc
48 use = egg:PasteDeploy#prefix
48 use = egg:PasteDeploy#prefix
49 prefix = /<your-prefix>
49 prefix = /<your-prefix>
50
50
51 [app:main]
51 [app:main]
52 use = egg:rhodecode
52 use = egg:rhodecode
53 #filter-with = proxy-prefix
53 #filter-with = proxy-prefix
54 full_stack = true
54 full_stack = true
55 static_files = true
55 static_files = true
56 # Optional Languages
56 # Optional Languages
57 # en, fr, ja, pt_BR, zh_CN, zh_TW
57 # en, fr, ja, pt_BR, zh_CN, zh_TW
58 lang = en
58 lang = en
59 cache_dir = %(here)s/data
59 cache_dir = %(here)s/data
60 index_dir = %(here)s/data/index
60 index_dir = %(here)s/data/index
61 app_instance_uuid = ${app_instance_uuid}
61 app_instance_uuid = ${app_instance_uuid}
62 cut_off_limit = 256000
62 cut_off_limit = 256000
63 force_https = false
63 force_https = false
64 commit_parse_limit = 50
64 commit_parse_limit = 50
65 use_gravatar = true
65 use_gravatar = true
66
66
67 ## alternative_gravatar_url allows you to use your own avatar server application
67 ## alternative_gravatar_url allows you to use your own avatar server application
68 ## the following parts of the URL will be replaced
68 ## the following parts of the URL will be replaced
69 ## {email} user email
69 ## {email} user email
70 ## {md5email} md5 hash of the user email (like at gravatar.com)
70 ## {md5email} md5 hash of the user email (like at gravatar.com)
71 ## {size} size of the image that is expected from the server application
71 ## {size} size of the image that is expected from the server application
72 ## {scheme} http/https from RhodeCode server
73 ## {netloc} network location from RhodeCode server
72 #alternative_gravatar_url = http://myavatarserver.com/getbyemail/{email}/{size}
74 #alternative_gravatar_url = http://myavatarserver.com/getbyemail/{email}/{size}
73 #alternative_gravatar_url = http://myavatarserver.com/getbymd5/{md5email}?s={size}
75 #alternative_gravatar_url = http://myavatarserver.com/getbymd5/{md5email}?s={size}
74
76
75 container_auth_enabled = false
77 container_auth_enabled = false
76 proxypass_auth_enabled = false
78 proxypass_auth_enabled = false
77 default_encoding = utf8
79 default_encoding = utf8
78
80
79 ## overwrite schema of clone url
81 ## overwrite schema of clone url
80 ## available vars:
82 ## available vars:
81 ## scheme - http/https
83 ## scheme - http/https
82 ## user - current user
84 ## user - current user
83 ## pass - password
85 ## pass - password
84 ## netloc - network location
86 ## netloc - network location
85 ## path - usually repo_name
87 ## path - usually repo_name
86
88
87 #clone_uri = {scheme}://{user}{pass}{netloc}{path}
89 #clone_uri = {scheme}://{user}{pass}{netloc}{path}
88
90
89 ## issue tracking mapping for commits messages
91 ## issue tracking mapping for commits messages
90 ## comment out issue_pat, issue_server, issue_prefix to enable
92 ## comment out issue_pat, issue_server, issue_prefix to enable
91
93
92 ## pattern to get the issues from commit messages
94 ## pattern to get the issues from commit messages
93 ## default one used here is #<numbers> with a regex passive group for `#`
95 ## default one used here is #<numbers> with a regex passive group for `#`
94 ## {id} will be all groups matched from this pattern
96 ## {id} will be all groups matched from this pattern
95
97
96 issue_pat = (?:\s*#)(\d+)
98 issue_pat = (?:\s*#)(\d+)
97
99
98 ## server url to the issue, each {id} will be replaced with match
100 ## server url to the issue, each {id} will be replaced with match
99 ## fetched from the regex and {repo} is replaced with full repository name
101 ## fetched from the regex and {repo} is replaced with full repository name
100 ## including groups {repo_name} is replaced with just name of repo
102 ## including groups {repo_name} is replaced with just name of repo
101
103
102 issue_server_link = https://myissueserver.com/{repo}/issue/{id}
104 issue_server_link = https://myissueserver.com/{repo}/issue/{id}
103
105
104 ## prefix to add to link to indicate it's an url
106 ## prefix to add to link to indicate it's an url
105 ## #314 will be replaced by <issue_prefix><id>
107 ## #314 will be replaced by <issue_prefix><id>
106
108
107 issue_prefix = #
109 issue_prefix = #
108
110
109 ## instance-id prefix
111 ## instance-id prefix
110 ## a prefix key for this instance used for cache invalidation when running
112 ## a prefix key for this instance used for cache invalidation when running
111 ## multiple instances of rhodecode, make sure it's globally unique for
113 ## multiple instances of rhodecode, make sure it's globally unique for
112 ## all running rhodecode instances. Leave empty if you don't use it
114 ## all running rhodecode instances. Leave empty if you don't use it
113 instance_id =
115 instance_id =
114
116
115 ## alternative return HTTP header for failed authentication. Default HTTP
117 ## alternative return HTTP header for failed authentication. Default HTTP
116 ## response is 401 HTTPUnauthorized. Currently HG clients have troubles with
118 ## response is 401 HTTPUnauthorized. Currently HG clients have troubles with
117 ## handling that. Set this variable to 403 to return HTTPForbidden
119 ## handling that. Set this variable to 403 to return HTTPForbidden
118 auth_ret_code =
120 auth_ret_code =
119
121
120 ####################################
122 ####################################
121 ### CELERY CONFIG ####
123 ### CELERY CONFIG ####
122 ####################################
124 ####################################
123 use_celery = false
125 use_celery = false
124 broker.host = localhost
126 broker.host = localhost
125 broker.vhost = rabbitmqhost
127 broker.vhost = rabbitmqhost
126 broker.port = 5672
128 broker.port = 5672
127 broker.user = rabbitmq
129 broker.user = rabbitmq
128 broker.password = qweqwe
130 broker.password = qweqwe
129
131
130 celery.imports = rhodecode.lib.celerylib.tasks
132 celery.imports = rhodecode.lib.celerylib.tasks
131
133
132 celery.result.backend = amqp
134 celery.result.backend = amqp
133 celery.result.dburi = amqp://
135 celery.result.dburi = amqp://
134 celery.result.serialier = json
136 celery.result.serialier = json
135
137
136 #celery.send.task.error.emails = true
138 #celery.send.task.error.emails = true
137 #celery.amqp.task.result.expires = 18000
139 #celery.amqp.task.result.expires = 18000
138
140
139 celeryd.concurrency = 2
141 celeryd.concurrency = 2
140 #celeryd.log.file = celeryd.log
142 #celeryd.log.file = celeryd.log
141 celeryd.log.level = debug
143 celeryd.log.level = debug
142 celeryd.max.tasks.per.child = 1
144 celeryd.max.tasks.per.child = 1
143
145
144 #tasks will never be sent to the queue, but executed locally instead.
146 #tasks will never be sent to the queue, but executed locally instead.
145 celery.always.eager = false
147 celery.always.eager = false
146
148
147 ####################################
149 ####################################
148 ### BEAKER CACHE ####
150 ### BEAKER CACHE ####
149 ####################################
151 ####################################
150 beaker.cache.data_dir=%(here)s/data/cache/data
152 beaker.cache.data_dir=%(here)s/data/cache/data
151 beaker.cache.lock_dir=%(here)s/data/cache/lock
153 beaker.cache.lock_dir=%(here)s/data/cache/lock
152
154
153 beaker.cache.regions=super_short_term,short_term,long_term,sql_cache_short,sql_cache_med,sql_cache_long
155 beaker.cache.regions=super_short_term,short_term,long_term,sql_cache_short,sql_cache_med,sql_cache_long
154
156
155 beaker.cache.super_short_term.type=memory
157 beaker.cache.super_short_term.type=memory
156 beaker.cache.super_short_term.expire=10
158 beaker.cache.super_short_term.expire=10
157 beaker.cache.super_short_term.key_length = 256
159 beaker.cache.super_short_term.key_length = 256
158
160
159 beaker.cache.short_term.type=memory
161 beaker.cache.short_term.type=memory
160 beaker.cache.short_term.expire=60
162 beaker.cache.short_term.expire=60
161 beaker.cache.short_term.key_length = 256
163 beaker.cache.short_term.key_length = 256
162
164
163 beaker.cache.long_term.type=memory
165 beaker.cache.long_term.type=memory
164 beaker.cache.long_term.expire=36000
166 beaker.cache.long_term.expire=36000
165 beaker.cache.long_term.key_length = 256
167 beaker.cache.long_term.key_length = 256
166
168
167 beaker.cache.sql_cache_short.type=memory
169 beaker.cache.sql_cache_short.type=memory
168 beaker.cache.sql_cache_short.expire=10
170 beaker.cache.sql_cache_short.expire=10
169 beaker.cache.sql_cache_short.key_length = 256
171 beaker.cache.sql_cache_short.key_length = 256
170
172
171 beaker.cache.sql_cache_med.type=memory
173 beaker.cache.sql_cache_med.type=memory
172 beaker.cache.sql_cache_med.expire=360
174 beaker.cache.sql_cache_med.expire=360
173 beaker.cache.sql_cache_med.key_length = 256
175 beaker.cache.sql_cache_med.key_length = 256
174
176
175 beaker.cache.sql_cache_long.type=file
177 beaker.cache.sql_cache_long.type=file
176 beaker.cache.sql_cache_long.expire=3600
178 beaker.cache.sql_cache_long.expire=3600
177 beaker.cache.sql_cache_long.key_length = 256
179 beaker.cache.sql_cache_long.key_length = 256
178
180
179 ####################################
181 ####################################
180 ### BEAKER SESSION ####
182 ### BEAKER SESSION ####
181 ####################################
183 ####################################
182 ## Type of storage used for the session, current types are
184 ## Type of storage used for the session, current types are
183 ## dbm, file, memcached, database, and memory.
185 ## dbm, file, memcached, database, and memory.
184 ## The storage uses the Container API
186 ## The storage uses the Container API
185 ## that is also used by the cache system.
187 ## that is also used by the cache system.
186
188
187 ## db session ##
189 ## db session ##
188 #beaker.session.type = ext:database
190 #beaker.session.type = ext:database
189 #beaker.session.sa.url = postgresql://postgres:qwe@localhost/rhodecode
191 #beaker.session.sa.url = postgresql://postgres:qwe@localhost/rhodecode
190 #beaker.session.table_name = db_session
192 #beaker.session.table_name = db_session
191
193
192 ## encrypted cookie client side session, good for many instances ##
194 ## encrypted cookie client side session, good for many instances ##
193 #beaker.session.type = cookie
195 #beaker.session.type = cookie
194
196
195 ## file based cookies (default) ##
197 ## file based cookies (default) ##
196 #beaker.session.type = file
198 #beaker.session.type = file
197
199
198
200
199 beaker.session.key = rhodecode
201 beaker.session.key = rhodecode
200 ## secure cookie requires AES python libraries ##
202 ## secure cookie requires AES python libraries ##
201 #beaker.session.encrypt_key = g654dcno0-9873jhgfreyu
203 #beaker.session.encrypt_key = g654dcno0-9873jhgfreyu
202 #beaker.session.validate_key = 9712sds2212c--zxc123
204 #beaker.session.validate_key = 9712sds2212c--zxc123
203 ## sets session as invalid if it haven't been accessed for given amount of time
205 ## sets session as invalid if it haven't been accessed for given amount of time
204 beaker.session.timeout = 2592000
206 beaker.session.timeout = 2592000
205 beaker.session.httponly = true
207 beaker.session.httponly = true
206 #beaker.session.cookie_path = /<your-prefix>
208 #beaker.session.cookie_path = /<your-prefix>
207
209
208 ## uncomment for https secure cookie ##
210 ## uncomment for https secure cookie ##
209 beaker.session.secure = false
211 beaker.session.secure = false
210
212
211 ## auto save the session to not to use .save() ##
213 ## auto save the session to not to use .save() ##
212 beaker.session.auto = False
214 beaker.session.auto = False
213
215
214 ## default cookie expiration time in seconds `true` expire at browser close ##
216 ## default cookie expiration time in seconds `true` expire at browser close ##
215 #beaker.session.cookie_expires = 3600
217 #beaker.session.cookie_expires = 3600
216
218
217
219
218 ################################################################################
220 ################################################################################
219 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
221 ## WARNING: *THE LINE BELOW MUST BE UNCOMMENTED ON A PRODUCTION ENVIRONMENT* ##
220 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
222 ## Debug mode will enable the interactive debugging tool, allowing ANYONE to ##
221 ## execute malicious code after an exception is raised. ##
223 ## execute malicious code after an exception is raised. ##
222 ################################################################################
224 ################################################################################
223 set debug = false
225 set debug = false
224
226
225 ##################################
227 ##################################
226 ### LOGVIEW CONFIG ###
228 ### LOGVIEW CONFIG ###
227 ##################################
229 ##################################
228 logview.sqlalchemy = #faa
230 logview.sqlalchemy = #faa
229 logview.pylons.templating = #bfb
231 logview.pylons.templating = #bfb
230 logview.pylons.util = #eee
232 logview.pylons.util = #eee
231
233
232 #########################################################
234 #########################################################
233 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
235 ### DB CONFIGS - EACH DB WILL HAVE IT'S OWN CONFIG ###
234 #########################################################
236 #########################################################
235
237
236 # SQLITE [default]
238 # SQLITE [default]
237 sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db
239 sqlalchemy.db1.url = sqlite:///%(here)s/rhodecode.db
238
240
239 # POSTGRESQL
241 # POSTGRESQL
240 # sqlalchemy.db1.url = postgresql://user:pass@localhost/rhodecode
242 # sqlalchemy.db1.url = postgresql://user:pass@localhost/rhodecode
241
243
242 # MySQL
244 # MySQL
243 # sqlalchemy.db1.url = mysql://user:pass@localhost/rhodecode
245 # sqlalchemy.db1.url = mysql://user:pass@localhost/rhodecode
244
246
245 # see sqlalchemy docs for others
247 # see sqlalchemy docs for others
246
248
247 sqlalchemy.db1.echo = false
249 sqlalchemy.db1.echo = false
248 sqlalchemy.db1.pool_recycle = 3600
250 sqlalchemy.db1.pool_recycle = 3600
249 sqlalchemy.db1.convert_unicode = true
251 sqlalchemy.db1.convert_unicode = true
250
252
251 ################################
253 ################################
252 ### LOGGING CONFIGURATION ####
254 ### LOGGING CONFIGURATION ####
253 ################################
255 ################################
254 [loggers]
256 [loggers]
255 keys = root, routes, rhodecode, sqlalchemy, beaker, templates, whoosh_indexer
257 keys = root, routes, rhodecode, sqlalchemy, beaker, templates, whoosh_indexer
256
258
257 [handlers]
259 [handlers]
258 keys = console, console_sql
260 keys = console, console_sql
259
261
260 [formatters]
262 [formatters]
261 keys = generic, color_formatter, color_formatter_sql
263 keys = generic, color_formatter, color_formatter_sql
262
264
263 #############
265 #############
264 ## LOGGERS ##
266 ## LOGGERS ##
265 #############
267 #############
266 [logger_root]
268 [logger_root]
267 level = NOTSET
269 level = NOTSET
268 handlers = console
270 handlers = console
269
271
270 [logger_routes]
272 [logger_routes]
271 level = DEBUG
273 level = DEBUG
272 handlers =
274 handlers =
273 qualname = routes.middleware
275 qualname = routes.middleware
274 # "level = DEBUG" logs the route matched and routing variables.
276 # "level = DEBUG" logs the route matched and routing variables.
275 propagate = 1
277 propagate = 1
276
278
277 [logger_beaker]
279 [logger_beaker]
278 level = DEBUG
280 level = DEBUG
279 handlers =
281 handlers =
280 qualname = beaker.container
282 qualname = beaker.container
281 propagate = 1
283 propagate = 1
282
284
283 [logger_templates]
285 [logger_templates]
284 level = INFO
286 level = INFO
285 handlers =
287 handlers =
286 qualname = pylons.templating
288 qualname = pylons.templating
287 propagate = 1
289 propagate = 1
288
290
289 [logger_rhodecode]
291 [logger_rhodecode]
290 level = DEBUG
292 level = DEBUG
291 handlers =
293 handlers =
292 qualname = rhodecode
294 qualname = rhodecode
293 propagate = 1
295 propagate = 1
294
296
295 [logger_sqlalchemy]
297 [logger_sqlalchemy]
296 level = INFO
298 level = INFO
297 handlers = console_sql
299 handlers = console_sql
298 qualname = sqlalchemy.engine
300 qualname = sqlalchemy.engine
299 propagate = 0
301 propagate = 0
300
302
301 [logger_whoosh_indexer]
303 [logger_whoosh_indexer]
302 level = DEBUG
304 level = DEBUG
303 handlers =
305 handlers =
304 qualname = whoosh_indexer
306 qualname = whoosh_indexer
305 propagate = 1
307 propagate = 1
306
308
307 ##############
309 ##############
308 ## HANDLERS ##
310 ## HANDLERS ##
309 ##############
311 ##############
310
312
311 [handler_console]
313 [handler_console]
312 class = StreamHandler
314 class = StreamHandler
313 args = (sys.stderr,)
315 args = (sys.stderr,)
314 level = INFO
316 level = INFO
315 formatter = generic
317 formatter = generic
316
318
317 [handler_console_sql]
319 [handler_console_sql]
318 class = StreamHandler
320 class = StreamHandler
319 args = (sys.stderr,)
321 args = (sys.stderr,)
320 level = WARN
322 level = WARN
321 formatter = generic
323 formatter = generic
322
324
323 ################
325 ################
324 ## FORMATTERS ##
326 ## FORMATTERS ##
325 ################
327 ################
326
328
327 [formatter_generic]
329 [formatter_generic]
328 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
330 format = %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
329 datefmt = %Y-%m-%d %H:%M:%S
331 datefmt = %Y-%m-%d %H:%M:%S
330
332
331 [formatter_color_formatter]
333 [formatter_color_formatter]
332 class=rhodecode.lib.colored_formatter.ColorFormatter
334 class=rhodecode.lib.colored_formatter.ColorFormatter
333 format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
335 format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
334 datefmt = %Y-%m-%d %H:%M:%S
336 datefmt = %Y-%m-%d %H:%M:%S
335
337
336 [formatter_color_formatter_sql]
338 [formatter_color_formatter_sql]
337 class=rhodecode.lib.colored_formatter.ColorFormatterSql
339 class=rhodecode.lib.colored_formatter.ColorFormatterSql
338 format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
340 format= %(asctime)s.%(msecs)03d %(levelname)-5.5s [%(name)s] %(message)s
339 datefmt = %Y-%m-%d %H:%M:%S
341 datefmt = %Y-%m-%d %H:%M:%S
@@ -1,580 +1,585 b''
1 """
1 """
2 Routes configuration
2 Routes configuration
3
3
4 The more specific and detailed routes should be defined first so they
4 The more specific and detailed routes should be defined first so they
5 may take precedent over the more generic routes. For more information
5 may take precedent over the more generic routes. For more information
6 refer to the routes manual at http://routes.groovie.org/docs/
6 refer to the routes manual at http://routes.groovie.org/docs/
7 """
7 """
8 from __future__ import with_statement
8 from __future__ import with_statement
9 from routes import Mapper
9 from routes import Mapper
10
10
11 # prefix for non repository related links needs to be prefixed with `/`
11 # prefix for non repository related links needs to be prefixed with `/`
12 ADMIN_PREFIX = '/_admin'
12 ADMIN_PREFIX = '/_admin'
13
13
14
14
15 def make_map(config):
15 def make_map(config):
16 """Create, configure and return the routes Mapper"""
16 """Create, configure and return the routes Mapper"""
17 rmap = Mapper(directory=config['pylons.paths']['controllers'],
17 rmap = Mapper(directory=config['pylons.paths']['controllers'],
18 always_scan=config['debug'])
18 always_scan=config['debug'])
19 rmap.minimization = False
19 rmap.minimization = False
20 rmap.explicit = False
20 rmap.explicit = False
21
21
22 from rhodecode.lib.utils import is_valid_repo
22 from rhodecode.lib.utils import is_valid_repo
23 from rhodecode.lib.utils import is_valid_repos_group
23 from rhodecode.lib.utils import is_valid_repos_group
24
24
25 def check_repo(environ, match_dict):
25 def check_repo(environ, match_dict):
26 """
26 """
27 check for valid repository for proper 404 handling
27 check for valid repository for proper 404 handling
28
28
29 :param environ:
29 :param environ:
30 :param match_dict:
30 :param match_dict:
31 """
31 """
32 from rhodecode.model.db import Repository
32 from rhodecode.model.db import Repository
33 repo_name = match_dict.get('repo_name')
33 repo_name = match_dict.get('repo_name')
34
34
35 try:
35 try:
36 by_id = repo_name.split('_')
36 by_id = repo_name.split('_')
37 if len(by_id) == 2 and by_id[1].isdigit() and by_id[0] == '':
37 if len(by_id) == 2 and by_id[1].isdigit() and by_id[0] == '':
38 repo_name = Repository.get(by_id[1]).repo_name
38 repo_name = Repository.get(by_id[1]).repo_name
39 match_dict['repo_name'] = repo_name
39 match_dict['repo_name'] = repo_name
40 except:
40 except:
41 pass
41 pass
42
42
43 return is_valid_repo(repo_name, config['base_path'])
43 return is_valid_repo(repo_name, config['base_path'])
44
44
45 def check_group(environ, match_dict):
45 def check_group(environ, match_dict):
46 """
46 """
47 check for valid repositories group for proper 404 handling
47 check for valid repositories group for proper 404 handling
48
48
49 :param environ:
49 :param environ:
50 :param match_dict:
50 :param match_dict:
51 """
51 """
52 repos_group_name = match_dict.get('group_name')
52 repos_group_name = match_dict.get('group_name')
53
53
54 return is_valid_repos_group(repos_group_name, config['base_path'])
54 return is_valid_repos_group(repos_group_name, config['base_path'])
55
55
56 def check_int(environ, match_dict):
56 def check_int(environ, match_dict):
57 return match_dict.get('id').isdigit()
57 return match_dict.get('id').isdigit()
58
58
59 # The ErrorController route (handles 404/500 error pages); it should
59 # The ErrorController route (handles 404/500 error pages); it should
60 # likely stay at the top, ensuring it can always be resolved
60 # likely stay at the top, ensuring it can always be resolved
61 rmap.connect('/error/{action}', controller='error')
61 rmap.connect('/error/{action}', controller='error')
62 rmap.connect('/error/{action}/{id}', controller='error')
62 rmap.connect('/error/{action}/{id}', controller='error')
63
63
64 #==========================================================================
64 #==========================================================================
65 # CUSTOM ROUTES HERE
65 # CUSTOM ROUTES HERE
66 #==========================================================================
66 #==========================================================================
67
67
68 #MAIN PAGE
68 #MAIN PAGE
69 rmap.connect('home', '/', controller='home', action='index')
69 rmap.connect('home', '/', controller='home', action='index')
70 rmap.connect('repo_switcher', '/repos', controller='home',
70 rmap.connect('repo_switcher', '/repos', controller='home',
71 action='repo_switcher')
71 action='repo_switcher')
72 rmap.connect('branch_tag_switcher', '/branches-tags/{repo_name:.*?}',
72 rmap.connect('branch_tag_switcher', '/branches-tags/{repo_name:.*?}',
73 controller='home', action='branch_tag_switcher')
73 controller='home', action='branch_tag_switcher')
74 rmap.connect('bugtracker',
74 rmap.connect('bugtracker',
75 "http://bitbucket.org/marcinkuzminski/rhodecode/issues",
75 "http://bitbucket.org/marcinkuzminski/rhodecode/issues",
76 _static=True)
76 _static=True)
77 rmap.connect('rst_help',
77 rmap.connect('rst_help',
78 "http://docutils.sourceforge.net/docs/user/rst/quickref.html",
78 "http://docutils.sourceforge.net/docs/user/rst/quickref.html",
79 _static=True)
79 _static=True)
80 rmap.connect('rhodecode_official', "http://rhodecode.org", _static=True)
80 rmap.connect('rhodecode_official', "http://rhodecode.org", _static=True)
81
81
82 #ADMIN REPOSITORY REST ROUTES
82 #ADMIN REPOSITORY REST ROUTES
83 with rmap.submapper(path_prefix=ADMIN_PREFIX,
83 with rmap.submapper(path_prefix=ADMIN_PREFIX,
84 controller='admin/repos') as m:
84 controller='admin/repos') as m:
85 m.connect("repos", "/repos",
85 m.connect("repos", "/repos",
86 action="create", conditions=dict(method=["POST"]))
86 action="create", conditions=dict(method=["POST"]))
87 m.connect("repos", "/repos",
87 m.connect("repos", "/repos",
88 action="index", conditions=dict(method=["GET"]))
88 action="index", conditions=dict(method=["GET"]))
89 m.connect("formatted_repos", "/repos.{format}",
89 m.connect("formatted_repos", "/repos.{format}",
90 action="index",
90 action="index",
91 conditions=dict(method=["GET"]))
91 conditions=dict(method=["GET"]))
92 m.connect("new_repo", "/repos/new",
92 m.connect("new_repo", "/repos/new",
93 action="new", conditions=dict(method=["GET"]))
93 action="new", conditions=dict(method=["GET"]))
94 m.connect("formatted_new_repo", "/repos/new.{format}",
94 m.connect("formatted_new_repo", "/repos/new.{format}",
95 action="new", conditions=dict(method=["GET"]))
95 action="new", conditions=dict(method=["GET"]))
96 m.connect("/repos/{repo_name:.*?}",
96 m.connect("/repos/{repo_name:.*?}",
97 action="update", conditions=dict(method=["PUT"],
97 action="update", conditions=dict(method=["PUT"],
98 function=check_repo))
98 function=check_repo))
99 m.connect("/repos/{repo_name:.*?}",
99 m.connect("/repos/{repo_name:.*?}",
100 action="delete", conditions=dict(method=["DELETE"],
100 action="delete", conditions=dict(method=["DELETE"],
101 function=check_repo))
101 function=check_repo))
102 m.connect("edit_repo", "/repos/{repo_name:.*?}/edit",
102 m.connect("edit_repo", "/repos/{repo_name:.*?}/edit",
103 action="edit", conditions=dict(method=["GET"],
103 action="edit", conditions=dict(method=["GET"],
104 function=check_repo))
104 function=check_repo))
105 m.connect("formatted_edit_repo", "/repos/{repo_name:.*?}.{format}/edit",
105 m.connect("formatted_edit_repo", "/repos/{repo_name:.*?}.{format}/edit",
106 action="edit", conditions=dict(method=["GET"],
106 action="edit", conditions=dict(method=["GET"],
107 function=check_repo))
107 function=check_repo))
108 m.connect("repo", "/repos/{repo_name:.*?}",
108 m.connect("repo", "/repos/{repo_name:.*?}",
109 action="show", conditions=dict(method=["GET"],
109 action="show", conditions=dict(method=["GET"],
110 function=check_repo))
110 function=check_repo))
111 m.connect("formatted_repo", "/repos/{repo_name:.*?}.{format}",
111 m.connect("formatted_repo", "/repos/{repo_name:.*?}.{format}",
112 action="show", conditions=dict(method=["GET"],
112 action="show", conditions=dict(method=["GET"],
113 function=check_repo))
113 function=check_repo))
114 #ajax delete repo perm user
114 #ajax delete repo perm user
115 m.connect('delete_repo_user', "/repos_delete_user/{repo_name:.*?}",
115 m.connect('delete_repo_user', "/repos_delete_user/{repo_name:.*?}",
116 action="delete_perm_user",
116 action="delete_perm_user",
117 conditions=dict(method=["DELETE"], function=check_repo))
117 conditions=dict(method=["DELETE"], function=check_repo))
118
118
119 #ajax delete repo perm users_group
119 #ajax delete repo perm users_group
120 m.connect('delete_repo_users_group',
120 m.connect('delete_repo_users_group',
121 "/repos_delete_users_group/{repo_name:.*?}",
121 "/repos_delete_users_group/{repo_name:.*?}",
122 action="delete_perm_users_group",
122 action="delete_perm_users_group",
123 conditions=dict(method=["DELETE"], function=check_repo))
123 conditions=dict(method=["DELETE"], function=check_repo))
124
124
125 #settings actions
125 #settings actions
126 m.connect('repo_stats', "/repos_stats/{repo_name:.*?}",
126 m.connect('repo_stats', "/repos_stats/{repo_name:.*?}",
127 action="repo_stats", conditions=dict(method=["DELETE"],
127 action="repo_stats", conditions=dict(method=["DELETE"],
128 function=check_repo))
128 function=check_repo))
129 m.connect('repo_cache', "/repos_cache/{repo_name:.*?}",
129 m.connect('repo_cache', "/repos_cache/{repo_name:.*?}",
130 action="repo_cache", conditions=dict(method=["DELETE"],
130 action="repo_cache", conditions=dict(method=["DELETE"],
131 function=check_repo))
131 function=check_repo))
132 m.connect('repo_public_journal', "/repos_public_journal/{repo_name:.*?}",
132 m.connect('repo_public_journal', "/repos_public_journal/{repo_name:.*?}",
133 action="repo_public_journal", conditions=dict(method=["PUT"],
133 action="repo_public_journal", conditions=dict(method=["PUT"],
134 function=check_repo))
134 function=check_repo))
135 m.connect('repo_pull', "/repo_pull/{repo_name:.*?}",
135 m.connect('repo_pull', "/repo_pull/{repo_name:.*?}",
136 action="repo_pull", conditions=dict(method=["PUT"],
136 action="repo_pull", conditions=dict(method=["PUT"],
137 function=check_repo))
137 function=check_repo))
138 m.connect('repo_as_fork', "/repo_as_fork/{repo_name:.*?}",
138 m.connect('repo_as_fork', "/repo_as_fork/{repo_name:.*?}",
139 action="repo_as_fork", conditions=dict(method=["PUT"],
139 action="repo_as_fork", conditions=dict(method=["PUT"],
140 function=check_repo))
140 function=check_repo))
141 m.connect('repo_locking', "/repo_locking/{repo_name:.*?}",
141 m.connect('repo_locking', "/repo_locking/{repo_name:.*?}",
142 action="repo_locking", conditions=dict(method=["PUT"],
142 action="repo_locking", conditions=dict(method=["PUT"],
143 function=check_repo))
143 function=check_repo))
144
144 with rmap.submapper(path_prefix=ADMIN_PREFIX,
145 with rmap.submapper(path_prefix=ADMIN_PREFIX,
145 controller='admin/repos_groups') as m:
146 controller='admin/repos_groups') as m:
146 m.connect("repos_groups", "/repos_groups",
147 m.connect("repos_groups", "/repos_groups",
147 action="create", conditions=dict(method=["POST"]))
148 action="create", conditions=dict(method=["POST"]))
148 m.connect("repos_groups", "/repos_groups",
149 m.connect("repos_groups", "/repos_groups",
149 action="index", conditions=dict(method=["GET"]))
150 action="index", conditions=dict(method=["GET"]))
150 m.connect("formatted_repos_groups", "/repos_groups.{format}",
151 m.connect("formatted_repos_groups", "/repos_groups.{format}",
151 action="index", conditions=dict(method=["GET"]))
152 action="index", conditions=dict(method=["GET"]))
152 m.connect("new_repos_group", "/repos_groups/new",
153 m.connect("new_repos_group", "/repos_groups/new",
153 action="new", conditions=dict(method=["GET"]))
154 action="new", conditions=dict(method=["GET"]))
154 m.connect("formatted_new_repos_group", "/repos_groups/new.{format}",
155 m.connect("formatted_new_repos_group", "/repos_groups/new.{format}",
155 action="new", conditions=dict(method=["GET"]))
156 action="new", conditions=dict(method=["GET"]))
156 m.connect("update_repos_group", "/repos_groups/{id}",
157 m.connect("update_repos_group", "/repos_groups/{id}",
157 action="update", conditions=dict(method=["PUT"],
158 action="update", conditions=dict(method=["PUT"],
158 function=check_int))
159 function=check_int))
159 m.connect("delete_repos_group", "/repos_groups/{id}",
160 m.connect("delete_repos_group", "/repos_groups/{id}",
160 action="delete", conditions=dict(method=["DELETE"],
161 action="delete", conditions=dict(method=["DELETE"],
161 function=check_int))
162 function=check_int))
162 m.connect("edit_repos_group", "/repos_groups/{id:.*?}/edit",
163 m.connect("edit_repos_group", "/repos_groups/{id:.*?}/edit",
163 action="edit", conditions=dict(method=["GET"],))
164 action="edit", conditions=dict(method=["GET"],))
164 m.connect("formatted_edit_repos_group",
165 m.connect("formatted_edit_repos_group",
165 "/repos_groups/{id}.{format}/edit",
166 "/repos_groups/{id}.{format}/edit",
166 action="edit", conditions=dict(method=["GET"],
167 action="edit", conditions=dict(method=["GET"],
167 function=check_int))
168 function=check_int))
168 m.connect("repos_group", "/repos_groups/{id}",
169 m.connect("repos_group", "/repos_groups/{id}",
169 action="show", conditions=dict(method=["GET"],
170 action="show", conditions=dict(method=["GET"],
170 function=check_int))
171 function=check_int))
171 m.connect("formatted_repos_group", "/repos_groups/{id}.{format}",
172 m.connect("formatted_repos_group", "/repos_groups/{id}.{format}",
172 action="show", conditions=dict(method=["GET"],
173 action="show", conditions=dict(method=["GET"],
173 function=check_int))
174 function=check_int))
174 # ajax delete repos group perm user
175 # ajax delete repos group perm user
175 m.connect('delete_repos_group_user_perm',
176 m.connect('delete_repos_group_user_perm',
176 "/delete_repos_group_user_perm/{group_name:.*}",
177 "/delete_repos_group_user_perm/{group_name:.*}",
177 action="delete_repos_group_user_perm",
178 action="delete_repos_group_user_perm",
178 conditions=dict(method=["DELETE"], function=check_group))
179 conditions=dict(method=["DELETE"], function=check_group))
179
180
180 # ajax delete repos group perm users_group
181 # ajax delete repos group perm users_group
181 m.connect('delete_repos_group_users_group_perm',
182 m.connect('delete_repos_group_users_group_perm',
182 "/delete_repos_group_users_group_perm/{group_name:.*}",
183 "/delete_repos_group_users_group_perm/{group_name:.*}",
183 action="delete_repos_group_users_group_perm",
184 action="delete_repos_group_users_group_perm",
184 conditions=dict(method=["DELETE"], function=check_group))
185 conditions=dict(method=["DELETE"], function=check_group))
185
186
186 #ADMIN USER REST ROUTES
187 #ADMIN USER REST ROUTES
187 with rmap.submapper(path_prefix=ADMIN_PREFIX,
188 with rmap.submapper(path_prefix=ADMIN_PREFIX,
188 controller='admin/users') as m:
189 controller='admin/users') as m:
189 m.connect("users", "/users",
190 m.connect("users", "/users",
190 action="create", conditions=dict(method=["POST"]))
191 action="create", conditions=dict(method=["POST"]))
191 m.connect("users", "/users",
192 m.connect("users", "/users",
192 action="index", conditions=dict(method=["GET"]))
193 action="index", conditions=dict(method=["GET"]))
193 m.connect("formatted_users", "/users.{format}",
194 m.connect("formatted_users", "/users.{format}",
194 action="index", conditions=dict(method=["GET"]))
195 action="index", conditions=dict(method=["GET"]))
195 m.connect("new_user", "/users/new",
196 m.connect("new_user", "/users/new",
196 action="new", conditions=dict(method=["GET"]))
197 action="new", conditions=dict(method=["GET"]))
197 m.connect("formatted_new_user", "/users/new.{format}",
198 m.connect("formatted_new_user", "/users/new.{format}",
198 action="new", conditions=dict(method=["GET"]))
199 action="new", conditions=dict(method=["GET"]))
199 m.connect("update_user", "/users/{id}",
200 m.connect("update_user", "/users/{id}",
200 action="update", conditions=dict(method=["PUT"]))
201 action="update", conditions=dict(method=["PUT"]))
201 m.connect("delete_user", "/users/{id}",
202 m.connect("delete_user", "/users/{id}",
202 action="delete", conditions=dict(method=["DELETE"]))
203 action="delete", conditions=dict(method=["DELETE"]))
203 m.connect("edit_user", "/users/{id}/edit",
204 m.connect("edit_user", "/users/{id}/edit",
204 action="edit", conditions=dict(method=["GET"]))
205 action="edit", conditions=dict(method=["GET"]))
205 m.connect("formatted_edit_user",
206 m.connect("formatted_edit_user",
206 "/users/{id}.{format}/edit",
207 "/users/{id}.{format}/edit",
207 action="edit", conditions=dict(method=["GET"]))
208 action="edit", conditions=dict(method=["GET"]))
208 m.connect("user", "/users/{id}",
209 m.connect("user", "/users/{id}",
209 action="show", conditions=dict(method=["GET"]))
210 action="show", conditions=dict(method=["GET"]))
210 m.connect("formatted_user", "/users/{id}.{format}",
211 m.connect("formatted_user", "/users/{id}.{format}",
211 action="show", conditions=dict(method=["GET"]))
212 action="show", conditions=dict(method=["GET"]))
212
213
213 #EXTRAS USER ROUTES
214 #EXTRAS USER ROUTES
214 m.connect("user_perm", "/users_perm/{id}",
215 m.connect("user_perm", "/users_perm/{id}",
215 action="update_perm", conditions=dict(method=["PUT"]))
216 action="update_perm", conditions=dict(method=["PUT"]))
216 m.connect("user_emails", "/users_emails/{id}",
217 m.connect("user_emails", "/users_emails/{id}",
217 action="add_email", conditions=dict(method=["PUT"]))
218 action="add_email", conditions=dict(method=["PUT"]))
218 m.connect("user_emails_delete", "/users_emails/{id}",
219 m.connect("user_emails_delete", "/users_emails/{id}",
219 action="delete_email", conditions=dict(method=["DELETE"]))
220 action="delete_email", conditions=dict(method=["DELETE"]))
220
221
221 #ADMIN USERS GROUPS REST ROUTES
222 #ADMIN USERS GROUPS REST ROUTES
222 with rmap.submapper(path_prefix=ADMIN_PREFIX,
223 with rmap.submapper(path_prefix=ADMIN_PREFIX,
223 controller='admin/users_groups') as m:
224 controller='admin/users_groups') as m:
224 m.connect("users_groups", "/users_groups",
225 m.connect("users_groups", "/users_groups",
225 action="create", conditions=dict(method=["POST"]))
226 action="create", conditions=dict(method=["POST"]))
226 m.connect("users_groups", "/users_groups",
227 m.connect("users_groups", "/users_groups",
227 action="index", conditions=dict(method=["GET"]))
228 action="index", conditions=dict(method=["GET"]))
228 m.connect("formatted_users_groups", "/users_groups.{format}",
229 m.connect("formatted_users_groups", "/users_groups.{format}",
229 action="index", conditions=dict(method=["GET"]))
230 action="index", conditions=dict(method=["GET"]))
230 m.connect("new_users_group", "/users_groups/new",
231 m.connect("new_users_group", "/users_groups/new",
231 action="new", conditions=dict(method=["GET"]))
232 action="new", conditions=dict(method=["GET"]))
232 m.connect("formatted_new_users_group", "/users_groups/new.{format}",
233 m.connect("formatted_new_users_group", "/users_groups/new.{format}",
233 action="new", conditions=dict(method=["GET"]))
234 action="new", conditions=dict(method=["GET"]))
234 m.connect("update_users_group", "/users_groups/{id}",
235 m.connect("update_users_group", "/users_groups/{id}",
235 action="update", conditions=dict(method=["PUT"]))
236 action="update", conditions=dict(method=["PUT"]))
236 m.connect("delete_users_group", "/users_groups/{id}",
237 m.connect("delete_users_group", "/users_groups/{id}",
237 action="delete", conditions=dict(method=["DELETE"]))
238 action="delete", conditions=dict(method=["DELETE"]))
238 m.connect("edit_users_group", "/users_groups/{id}/edit",
239 m.connect("edit_users_group", "/users_groups/{id}/edit",
239 action="edit", conditions=dict(method=["GET"]))
240 action="edit", conditions=dict(method=["GET"]))
240 m.connect("formatted_edit_users_group",
241 m.connect("formatted_edit_users_group",
241 "/users_groups/{id}.{format}/edit",
242 "/users_groups/{id}.{format}/edit",
242 action="edit", conditions=dict(method=["GET"]))
243 action="edit", conditions=dict(method=["GET"]))
243 m.connect("users_group", "/users_groups/{id}",
244 m.connect("users_group", "/users_groups/{id}",
244 action="show", conditions=dict(method=["GET"]))
245 action="show", conditions=dict(method=["GET"]))
245 m.connect("formatted_users_group", "/users_groups/{id}.{format}",
246 m.connect("formatted_users_group", "/users_groups/{id}.{format}",
246 action="show", conditions=dict(method=["GET"]))
247 action="show", conditions=dict(method=["GET"]))
247
248
248 #EXTRAS USER ROUTES
249 #EXTRAS USER ROUTES
249 m.connect("users_group_perm", "/users_groups_perm/{id}",
250 m.connect("users_group_perm", "/users_groups_perm/{id}",
250 action="update_perm", conditions=dict(method=["PUT"]))
251 action="update_perm", conditions=dict(method=["PUT"]))
251
252
252 #ADMIN GROUP REST ROUTES
253 #ADMIN GROUP REST ROUTES
253 rmap.resource('group', 'groups',
254 rmap.resource('group', 'groups',
254 controller='admin/groups', path_prefix=ADMIN_PREFIX)
255 controller='admin/groups', path_prefix=ADMIN_PREFIX)
255
256
256 #ADMIN PERMISSIONS REST ROUTES
257 #ADMIN PERMISSIONS REST ROUTES
257 rmap.resource('permission', 'permissions',
258 rmap.resource('permission', 'permissions',
258 controller='admin/permissions', path_prefix=ADMIN_PREFIX)
259 controller='admin/permissions', path_prefix=ADMIN_PREFIX)
259
260
260 ##ADMIN LDAP SETTINGS
261 ##ADMIN LDAP SETTINGS
261 rmap.connect('ldap_settings', '%s/ldap' % ADMIN_PREFIX,
262 rmap.connect('ldap_settings', '%s/ldap' % ADMIN_PREFIX,
262 controller='admin/ldap_settings', action='ldap_settings',
263 controller='admin/ldap_settings', action='ldap_settings',
263 conditions=dict(method=["POST"]))
264 conditions=dict(method=["POST"]))
264
265
265 rmap.connect('ldap_home', '%s/ldap' % ADMIN_PREFIX,
266 rmap.connect('ldap_home', '%s/ldap' % ADMIN_PREFIX,
266 controller='admin/ldap_settings')
267 controller='admin/ldap_settings')
267
268
268 #ADMIN SETTINGS REST ROUTES
269 #ADMIN SETTINGS REST ROUTES
269 with rmap.submapper(path_prefix=ADMIN_PREFIX,
270 with rmap.submapper(path_prefix=ADMIN_PREFIX,
270 controller='admin/settings') as m:
271 controller='admin/settings') as m:
271 m.connect("admin_settings", "/settings",
272 m.connect("admin_settings", "/settings",
272 action="create", conditions=dict(method=["POST"]))
273 action="create", conditions=dict(method=["POST"]))
273 m.connect("admin_settings", "/settings",
274 m.connect("admin_settings", "/settings",
274 action="index", conditions=dict(method=["GET"]))
275 action="index", conditions=dict(method=["GET"]))
275 m.connect("formatted_admin_settings", "/settings.{format}",
276 m.connect("formatted_admin_settings", "/settings.{format}",
276 action="index", conditions=dict(method=["GET"]))
277 action="index", conditions=dict(method=["GET"]))
277 m.connect("admin_new_setting", "/settings/new",
278 m.connect("admin_new_setting", "/settings/new",
278 action="new", conditions=dict(method=["GET"]))
279 action="new", conditions=dict(method=["GET"]))
279 m.connect("formatted_admin_new_setting", "/settings/new.{format}",
280 m.connect("formatted_admin_new_setting", "/settings/new.{format}",
280 action="new", conditions=dict(method=["GET"]))
281 action="new", conditions=dict(method=["GET"]))
281 m.connect("/settings/{setting_id}",
282 m.connect("/settings/{setting_id}",
282 action="update", conditions=dict(method=["PUT"]))
283 action="update", conditions=dict(method=["PUT"]))
283 m.connect("/settings/{setting_id}",
284 m.connect("/settings/{setting_id}",
284 action="delete", conditions=dict(method=["DELETE"]))
285 action="delete", conditions=dict(method=["DELETE"]))
285 m.connect("admin_edit_setting", "/settings/{setting_id}/edit",
286 m.connect("admin_edit_setting", "/settings/{setting_id}/edit",
286 action="edit", conditions=dict(method=["GET"]))
287 action="edit", conditions=dict(method=["GET"]))
287 m.connect("formatted_admin_edit_setting",
288 m.connect("formatted_admin_edit_setting",
288 "/settings/{setting_id}.{format}/edit",
289 "/settings/{setting_id}.{format}/edit",
289 action="edit", conditions=dict(method=["GET"]))
290 action="edit", conditions=dict(method=["GET"]))
290 m.connect("admin_setting", "/settings/{setting_id}",
291 m.connect("admin_setting", "/settings/{setting_id}",
291 action="show", conditions=dict(method=["GET"]))
292 action="show", conditions=dict(method=["GET"]))
292 m.connect("formatted_admin_setting", "/settings/{setting_id}.{format}",
293 m.connect("formatted_admin_setting", "/settings/{setting_id}.{format}",
293 action="show", conditions=dict(method=["GET"]))
294 action="show", conditions=dict(method=["GET"]))
294 m.connect("admin_settings_my_account", "/my_account",
295 m.connect("admin_settings_my_account", "/my_account",
295 action="my_account", conditions=dict(method=["GET"]))
296 action="my_account", conditions=dict(method=["GET"]))
296 m.connect("admin_settings_my_account_update", "/my_account_update",
297 m.connect("admin_settings_my_account_update", "/my_account_update",
297 action="my_account_update", conditions=dict(method=["PUT"]))
298 action="my_account_update", conditions=dict(method=["PUT"]))
298 m.connect("admin_settings_create_repository", "/create_repository",
299 m.connect("admin_settings_create_repository", "/create_repository",
299 action="create_repository", conditions=dict(method=["GET"]))
300 action="create_repository", conditions=dict(method=["GET"]))
300 m.connect("admin_settings_my_repos", "/my_account/repos",
301 m.connect("admin_settings_my_repos", "/my_account/repos",
301 action="my_account_my_repos", conditions=dict(method=["GET"]))
302 action="my_account_my_repos", conditions=dict(method=["GET"]))
302 m.connect("admin_settings_my_pullrequests", "/my_account/pull_requests",
303 m.connect("admin_settings_my_pullrequests", "/my_account/pull_requests",
303 action="my_account_my_pullrequests", conditions=dict(method=["GET"]))
304 action="my_account_my_pullrequests", conditions=dict(method=["GET"]))
304
305
305 #NOTIFICATION REST ROUTES
306 #NOTIFICATION REST ROUTES
306 with rmap.submapper(path_prefix=ADMIN_PREFIX,
307 with rmap.submapper(path_prefix=ADMIN_PREFIX,
307 controller='admin/notifications') as m:
308 controller='admin/notifications') as m:
308 m.connect("notifications", "/notifications",
309 m.connect("notifications", "/notifications",
309 action="create", conditions=dict(method=["POST"]))
310 action="create", conditions=dict(method=["POST"]))
310 m.connect("notifications", "/notifications",
311 m.connect("notifications", "/notifications",
311 action="index", conditions=dict(method=["GET"]))
312 action="index", conditions=dict(method=["GET"]))
312 m.connect("notifications_mark_all_read", "/notifications/mark_all_read",
313 m.connect("notifications_mark_all_read", "/notifications/mark_all_read",
313 action="mark_all_read", conditions=dict(method=["GET"]))
314 action="mark_all_read", conditions=dict(method=["GET"]))
314 m.connect("formatted_notifications", "/notifications.{format}",
315 m.connect("formatted_notifications", "/notifications.{format}",
315 action="index", conditions=dict(method=["GET"]))
316 action="index", conditions=dict(method=["GET"]))
316 m.connect("new_notification", "/notifications/new",
317 m.connect("new_notification", "/notifications/new",
317 action="new", conditions=dict(method=["GET"]))
318 action="new", conditions=dict(method=["GET"]))
318 m.connect("formatted_new_notification", "/notifications/new.{format}",
319 m.connect("formatted_new_notification", "/notifications/new.{format}",
319 action="new", conditions=dict(method=["GET"]))
320 action="new", conditions=dict(method=["GET"]))
320 m.connect("/notification/{notification_id}",
321 m.connect("/notification/{notification_id}",
321 action="update", conditions=dict(method=["PUT"]))
322 action="update", conditions=dict(method=["PUT"]))
322 m.connect("/notification/{notification_id}",
323 m.connect("/notification/{notification_id}",
323 action="delete", conditions=dict(method=["DELETE"]))
324 action="delete", conditions=dict(method=["DELETE"]))
324 m.connect("edit_notification", "/notification/{notification_id}/edit",
325 m.connect("edit_notification", "/notification/{notification_id}/edit",
325 action="edit", conditions=dict(method=["GET"]))
326 action="edit", conditions=dict(method=["GET"]))
326 m.connect("formatted_edit_notification",
327 m.connect("formatted_edit_notification",
327 "/notification/{notification_id}.{format}/edit",
328 "/notification/{notification_id}.{format}/edit",
328 action="edit", conditions=dict(method=["GET"]))
329 action="edit", conditions=dict(method=["GET"]))
329 m.connect("notification", "/notification/{notification_id}",
330 m.connect("notification", "/notification/{notification_id}",
330 action="show", conditions=dict(method=["GET"]))
331 action="show", conditions=dict(method=["GET"]))
331 m.connect("formatted_notification", "/notifications/{notification_id}.{format}",
332 m.connect("formatted_notification", "/notifications/{notification_id}.{format}",
332 action="show", conditions=dict(method=["GET"]))
333 action="show", conditions=dict(method=["GET"]))
333
334
334 #ADMIN MAIN PAGES
335 #ADMIN MAIN PAGES
335 with rmap.submapper(path_prefix=ADMIN_PREFIX,
336 with rmap.submapper(path_prefix=ADMIN_PREFIX,
336 controller='admin/admin') as m:
337 controller='admin/admin') as m:
337 m.connect('admin_home', '', action='index')
338 m.connect('admin_home', '', action='index')
338 m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
339 m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
339 action='add_repo')
340 action='add_repo')
340
341
341 #==========================================================================
342 #==========================================================================
342 # API V2
343 # API V2
343 #==========================================================================
344 #==========================================================================
344 with rmap.submapper(path_prefix=ADMIN_PREFIX,
345 with rmap.submapper(path_prefix=ADMIN_PREFIX,
345 controller='api/api') as m:
346 controller='api/api') as m:
346 m.connect('api', '/api')
347 m.connect('api', '/api')
347
348
348 #USER JOURNAL
349 #USER JOURNAL
349 rmap.connect('journal', '%s/journal' % ADMIN_PREFIX,
350 rmap.connect('journal', '%s/journal' % ADMIN_PREFIX,
350 controller='journal', action='index')
351 controller='journal', action='index')
351 rmap.connect('journal_rss', '%s/journal/rss' % ADMIN_PREFIX,
352 rmap.connect('journal_rss', '%s/journal/rss' % ADMIN_PREFIX,
352 controller='journal', action='journal_rss')
353 controller='journal', action='journal_rss')
353 rmap.connect('journal_atom', '%s/journal/atom' % ADMIN_PREFIX,
354 rmap.connect('journal_atom', '%s/journal/atom' % ADMIN_PREFIX,
354 controller='journal', action='journal_atom')
355 controller='journal', action='journal_atom')
355
356
356 rmap.connect('public_journal', '%s/public_journal' % ADMIN_PREFIX,
357 rmap.connect('public_journal', '%s/public_journal' % ADMIN_PREFIX,
357 controller='journal', action="public_journal")
358 controller='journal', action="public_journal")
358
359
359 rmap.connect('public_journal_rss', '%s/public_journal/rss' % ADMIN_PREFIX,
360 rmap.connect('public_journal_rss', '%s/public_journal/rss' % ADMIN_PREFIX,
360 controller='journal', action="public_journal_rss")
361 controller='journal', action="public_journal_rss")
361
362
362 rmap.connect('public_journal_rss_old', '%s/public_journal_rss' % ADMIN_PREFIX,
363 rmap.connect('public_journal_rss_old', '%s/public_journal_rss' % ADMIN_PREFIX,
363 controller='journal', action="public_journal_rss")
364 controller='journal', action="public_journal_rss")
364
365
365 rmap.connect('public_journal_atom',
366 rmap.connect('public_journal_atom',
366 '%s/public_journal/atom' % ADMIN_PREFIX, controller='journal',
367 '%s/public_journal/atom' % ADMIN_PREFIX, controller='journal',
367 action="public_journal_atom")
368 action="public_journal_atom")
368
369
369 rmap.connect('public_journal_atom_old',
370 rmap.connect('public_journal_atom_old',
370 '%s/public_journal_atom' % ADMIN_PREFIX, controller='journal',
371 '%s/public_journal_atom' % ADMIN_PREFIX, controller='journal',
371 action="public_journal_atom")
372 action="public_journal_atom")
372
373
373 rmap.connect('toggle_following', '%s/toggle_following' % ADMIN_PREFIX,
374 rmap.connect('toggle_following', '%s/toggle_following' % ADMIN_PREFIX,
374 controller='journal', action='toggle_following',
375 controller='journal', action='toggle_following',
375 conditions=dict(method=["POST"]))
376 conditions=dict(method=["POST"]))
376
377
377 #SEARCH
378 #SEARCH
378 rmap.connect('search', '%s/search' % ADMIN_PREFIX, controller='search',)
379 rmap.connect('search', '%s/search' % ADMIN_PREFIX, controller='search',)
379 rmap.connect('search_repo', '%s/search/{search_repo:.*}' % ADMIN_PREFIX,
380 rmap.connect('search_repo', '%s/search/{search_repo:.*}' % ADMIN_PREFIX,
380 controller='search')
381 controller='search')
381
382
382 #LOGIN/LOGOUT/REGISTER/SIGN IN
383 #LOGIN/LOGOUT/REGISTER/SIGN IN
383 rmap.connect('login_home', '%s/login' % ADMIN_PREFIX, controller='login')
384 rmap.connect('login_home', '%s/login' % ADMIN_PREFIX, controller='login')
384 rmap.connect('logout_home', '%s/logout' % ADMIN_PREFIX, controller='login',
385 rmap.connect('logout_home', '%s/logout' % ADMIN_PREFIX, controller='login',
385 action='logout')
386 action='logout')
386
387
387 rmap.connect('register', '%s/register' % ADMIN_PREFIX, controller='login',
388 rmap.connect('register', '%s/register' % ADMIN_PREFIX, controller='login',
388 action='register')
389 action='register')
389
390
390 rmap.connect('reset_password', '%s/password_reset' % ADMIN_PREFIX,
391 rmap.connect('reset_password', '%s/password_reset' % ADMIN_PREFIX,
391 controller='login', action='password_reset')
392 controller='login', action='password_reset')
392
393
393 rmap.connect('reset_password_confirmation',
394 rmap.connect('reset_password_confirmation',
394 '%s/password_reset_confirmation' % ADMIN_PREFIX,
395 '%s/password_reset_confirmation' % ADMIN_PREFIX,
395 controller='login', action='password_reset_confirmation')
396 controller='login', action='password_reset_confirmation')
396
397
397 #FEEDS
398 #FEEDS
398 rmap.connect('rss_feed_home', '/{repo_name:.*?}/feed/rss',
399 rmap.connect('rss_feed_home', '/{repo_name:.*?}/feed/rss',
399 controller='feed', action='rss',
400 controller='feed', action='rss',
400 conditions=dict(function=check_repo))
401 conditions=dict(function=check_repo))
401
402
402 rmap.connect('atom_feed_home', '/{repo_name:.*?}/feed/atom',
403 rmap.connect('atom_feed_home', '/{repo_name:.*?}/feed/atom',
403 controller='feed', action='atom',
404 controller='feed', action='atom',
404 conditions=dict(function=check_repo))
405 conditions=dict(function=check_repo))
405
406
406 #==========================================================================
407 #==========================================================================
407 # REPOSITORY ROUTES
408 # REPOSITORY ROUTES
408 #==========================================================================
409 #==========================================================================
409 rmap.connect('summary_home', '/{repo_name:.*?}',
410 rmap.connect('summary_home', '/{repo_name:.*?}',
410 controller='summary',
411 controller='summary',
411 conditions=dict(function=check_repo))
412 conditions=dict(function=check_repo))
412
413
413 rmap.connect('repos_group_home', '/{group_name:.*}',
414 rmap.connect('repos_group_home', '/{group_name:.*}',
414 controller='admin/repos_groups', action="show_by_name",
415 controller='admin/repos_groups', action="show_by_name",
415 conditions=dict(function=check_group))
416 conditions=dict(function=check_group))
416
417
417 rmap.connect('changeset_home', '/{repo_name:.*?}/changeset/{revision}',
418 rmap.connect('changeset_home', '/{repo_name:.*?}/changeset/{revision}',
418 controller='changeset', revision='tip',
419 controller='changeset', revision='tip',
419 conditions=dict(function=check_repo))
420 conditions=dict(function=check_repo))
420
421
421 rmap.connect('changeset_comment',
422 rmap.connect('changeset_comment',
422 '/{repo_name:.*?}/changeset/{revision}/comment',
423 '/{repo_name:.*?}/changeset/{revision}/comment',
423 controller='changeset', revision='tip', action='comment',
424 controller='changeset', revision='tip', action='comment',
424 conditions=dict(function=check_repo))
425 conditions=dict(function=check_repo))
425
426
426 rmap.connect('changeset_comment_delete',
427 rmap.connect('changeset_comment_delete',
427 '/{repo_name:.*?}/changeset/comment/{comment_id}/delete',
428 '/{repo_name:.*?}/changeset/comment/{comment_id}/delete',
428 controller='changeset', action='delete_comment',
429 controller='changeset', action='delete_comment',
429 conditions=dict(function=check_repo, method=["DELETE"]))
430 conditions=dict(function=check_repo, method=["DELETE"]))
430
431
431 rmap.connect('raw_changeset_home',
432 rmap.connect('raw_changeset_home',
432 '/{repo_name:.*?}/raw-changeset/{revision}',
433 '/{repo_name:.*?}/raw-changeset/{revision}',
433 controller='changeset', action='raw_changeset',
434 controller='changeset', action='raw_changeset',
434 revision='tip', conditions=dict(function=check_repo))
435 revision='tip', conditions=dict(function=check_repo))
435
436
436 rmap.connect('compare_url',
437 rmap.connect('compare_url',
437 '/{repo_name:.*?}/compare/{org_ref_type}@{org_ref}...{other_ref_type}@{other_ref}',
438 '/{repo_name:.*?}/compare/{org_ref_type}@{org_ref}...{other_ref_type}@{other_ref}',
438 controller='compare', action='index',
439 controller='compare', action='index',
439 conditions=dict(function=check_repo),
440 conditions=dict(function=check_repo),
440 requirements=dict(
441 requirements=dict(
441 org_ref_type='(branch|book|tag|rev|org_ref_type)',
442 org_ref_type='(branch|book|tag|rev|org_ref_type)',
442 other_ref_type='(branch|book|tag|rev|other_ref_type)')
443 other_ref_type='(branch|book|tag|rev|other_ref_type)')
443 )
444 )
444
445
445 rmap.connect('pullrequest_home',
446 rmap.connect('pullrequest_home',
446 '/{repo_name:.*?}/pull-request/new', controller='pullrequests',
447 '/{repo_name:.*?}/pull-request/new', controller='pullrequests',
447 action='index', conditions=dict(function=check_repo,
448 action='index', conditions=dict(function=check_repo,
448 method=["GET"]))
449 method=["GET"]))
449
450
450 rmap.connect('pullrequest',
451 rmap.connect('pullrequest',
451 '/{repo_name:.*?}/pull-request/new', controller='pullrequests',
452 '/{repo_name:.*?}/pull-request/new', controller='pullrequests',
452 action='create', conditions=dict(function=check_repo,
453 action='create', conditions=dict(function=check_repo,
453 method=["POST"]))
454 method=["POST"]))
454
455
455 rmap.connect('pullrequest_show',
456 rmap.connect('pullrequest_show',
456 '/{repo_name:.*?}/pull-request/{pull_request_id}',
457 '/{repo_name:.*?}/pull-request/{pull_request_id}',
457 controller='pullrequests',
458 controller='pullrequests',
458 action='show', conditions=dict(function=check_repo,
459 action='show', conditions=dict(function=check_repo,
459 method=["GET"]))
460 method=["GET"]))
460 rmap.connect('pullrequest_update',
461 rmap.connect('pullrequest_update',
461 '/{repo_name:.*?}/pull-request/{pull_request_id}',
462 '/{repo_name:.*?}/pull-request/{pull_request_id}',
462 controller='pullrequests',
463 controller='pullrequests',
463 action='update', conditions=dict(function=check_repo,
464 action='update', conditions=dict(function=check_repo,
464 method=["PUT"]))
465 method=["PUT"]))
465 rmap.connect('pullrequest_delete',
466 rmap.connect('pullrequest_delete',
466 '/{repo_name:.*?}/pull-request/{pull_request_id}',
467 '/{repo_name:.*?}/pull-request/{pull_request_id}',
467 controller='pullrequests',
468 controller='pullrequests',
468 action='delete', conditions=dict(function=check_repo,
469 action='delete', conditions=dict(function=check_repo,
469 method=["DELETE"]))
470 method=["DELETE"]))
470
471
471 rmap.connect('pullrequest_show_all',
472 rmap.connect('pullrequest_show_all',
472 '/{repo_name:.*?}/pull-request',
473 '/{repo_name:.*?}/pull-request',
473 controller='pullrequests',
474 controller='pullrequests',
474 action='show_all', conditions=dict(function=check_repo,
475 action='show_all', conditions=dict(function=check_repo,
475 method=["GET"]))
476 method=["GET"]))
476
477
477 rmap.connect('pullrequest_comment',
478 rmap.connect('pullrequest_comment',
478 '/{repo_name:.*?}/pull-request-comment/{pull_request_id}',
479 '/{repo_name:.*?}/pull-request-comment/{pull_request_id}',
479 controller='pullrequests',
480 controller='pullrequests',
480 action='comment', conditions=dict(function=check_repo,
481 action='comment', conditions=dict(function=check_repo,
481 method=["POST"]))
482 method=["POST"]))
482
483
483 rmap.connect('pullrequest_comment_delete',
484 rmap.connect('pullrequest_comment_delete',
484 '/{repo_name:.*?}/pull-request-comment/{comment_id}/delete',
485 '/{repo_name:.*?}/pull-request-comment/{comment_id}/delete',
485 controller='pullrequests', action='delete_comment',
486 controller='pullrequests', action='delete_comment',
486 conditions=dict(function=check_repo, method=["DELETE"]))
487 conditions=dict(function=check_repo, method=["DELETE"]))
487
488
488 rmap.connect('summary_home', '/{repo_name:.*?}/summary',
489 rmap.connect('summary_home', '/{repo_name:.*?}/summary',
489 controller='summary', conditions=dict(function=check_repo))
490 controller='summary', conditions=dict(function=check_repo))
490
491
491 rmap.connect('shortlog_home', '/{repo_name:.*?}/shortlog',
492 rmap.connect('shortlog_home', '/{repo_name:.*?}/shortlog',
492 controller='shortlog', conditions=dict(function=check_repo))
493 controller='shortlog', conditions=dict(function=check_repo))
493
494
494 rmap.connect('branches_home', '/{repo_name:.*?}/branches',
495 rmap.connect('branches_home', '/{repo_name:.*?}/branches',
495 controller='branches', conditions=dict(function=check_repo))
496 controller='branches', conditions=dict(function=check_repo))
496
497
497 rmap.connect('tags_home', '/{repo_name:.*?}/tags',
498 rmap.connect('tags_home', '/{repo_name:.*?}/tags',
498 controller='tags', conditions=dict(function=check_repo))
499 controller='tags', conditions=dict(function=check_repo))
499
500
500 rmap.connect('bookmarks_home', '/{repo_name:.*?}/bookmarks',
501 rmap.connect('bookmarks_home', '/{repo_name:.*?}/bookmarks',
501 controller='bookmarks', conditions=dict(function=check_repo))
502 controller='bookmarks', conditions=dict(function=check_repo))
502
503
503 rmap.connect('changelog_home', '/{repo_name:.*?}/changelog',
504 rmap.connect('changelog_home', '/{repo_name:.*?}/changelog',
504 controller='changelog', conditions=dict(function=check_repo))
505 controller='changelog', conditions=dict(function=check_repo))
505
506
506 rmap.connect('changelog_details', '/{repo_name:.*?}/changelog_details/{cs}',
507 rmap.connect('changelog_details', '/{repo_name:.*?}/changelog_details/{cs}',
507 controller='changelog', action='changelog_details',
508 controller='changelog', action='changelog_details',
508 conditions=dict(function=check_repo))
509 conditions=dict(function=check_repo))
509
510
510 rmap.connect('files_home', '/{repo_name:.*?}/files/{revision}/{f_path:.*}',
511 rmap.connect('files_home', '/{repo_name:.*?}/files/{revision}/{f_path:.*}',
511 controller='files', revision='tip', f_path='',
512 controller='files', revision='tip', f_path='',
512 conditions=dict(function=check_repo))
513 conditions=dict(function=check_repo))
513
514
514 rmap.connect('files_diff_home', '/{repo_name:.*?}/diff/{f_path:.*}',
515 rmap.connect('files_diff_home', '/{repo_name:.*?}/diff/{f_path:.*}',
515 controller='files', action='diff', revision='tip', f_path='',
516 controller='files', action='diff', revision='tip', f_path='',
516 conditions=dict(function=check_repo))
517 conditions=dict(function=check_repo))
517
518
518 rmap.connect('files_rawfile_home',
519 rmap.connect('files_rawfile_home',
519 '/{repo_name:.*?}/rawfile/{revision}/{f_path:.*}',
520 '/{repo_name:.*?}/rawfile/{revision}/{f_path:.*}',
520 controller='files', action='rawfile', revision='tip',
521 controller='files', action='rawfile', revision='tip',
521 f_path='', conditions=dict(function=check_repo))
522 f_path='', conditions=dict(function=check_repo))
522
523
523 rmap.connect('files_raw_home',
524 rmap.connect('files_raw_home',
524 '/{repo_name:.*?}/raw/{revision}/{f_path:.*}',
525 '/{repo_name:.*?}/raw/{revision}/{f_path:.*}',
525 controller='files', action='raw', revision='tip', f_path='',
526 controller='files', action='raw', revision='tip', f_path='',
526 conditions=dict(function=check_repo))
527 conditions=dict(function=check_repo))
527
528
528 rmap.connect('files_annotate_home',
529 rmap.connect('files_annotate_home',
529 '/{repo_name:.*?}/annotate/{revision}/{f_path:.*}',
530 '/{repo_name:.*?}/annotate/{revision}/{f_path:.*}',
530 controller='files', action='index', revision='tip',
531 controller='files', action='index', revision='tip',
531 f_path='', annotate=True, conditions=dict(function=check_repo))
532 f_path='', annotate=True, conditions=dict(function=check_repo))
532
533
533 rmap.connect('files_edit_home',
534 rmap.connect('files_edit_home',
534 '/{repo_name:.*?}/edit/{revision}/{f_path:.*}',
535 '/{repo_name:.*?}/edit/{revision}/{f_path:.*}',
535 controller='files', action='edit', revision='tip',
536 controller='files', action='edit', revision='tip',
536 f_path='', conditions=dict(function=check_repo))
537 f_path='', conditions=dict(function=check_repo))
537
538
538 rmap.connect('files_add_home',
539 rmap.connect('files_add_home',
539 '/{repo_name:.*?}/add/{revision}/{f_path:.*}',
540 '/{repo_name:.*?}/add/{revision}/{f_path:.*}',
540 controller='files', action='add', revision='tip',
541 controller='files', action='add', revision='tip',
541 f_path='', conditions=dict(function=check_repo))
542 f_path='', conditions=dict(function=check_repo))
542
543
543 rmap.connect('files_archive_home', '/{repo_name:.*?}/archive/{fname}',
544 rmap.connect('files_archive_home', '/{repo_name:.*?}/archive/{fname}',
544 controller='files', action='archivefile',
545 controller='files', action='archivefile',
545 conditions=dict(function=check_repo))
546 conditions=dict(function=check_repo))
546
547
547 rmap.connect('files_nodelist_home',
548 rmap.connect('files_nodelist_home',
548 '/{repo_name:.*?}/nodelist/{revision}/{f_path:.*}',
549 '/{repo_name:.*?}/nodelist/{revision}/{f_path:.*}',
549 controller='files', action='nodelist',
550 controller='files', action='nodelist',
550 conditions=dict(function=check_repo))
551 conditions=dict(function=check_repo))
551
552
552 rmap.connect('repo_settings_delete', '/{repo_name:.*?}/settings',
553 rmap.connect('repo_settings_delete', '/{repo_name:.*?}/settings',
553 controller='settings', action="delete",
554 controller='settings', action="delete",
554 conditions=dict(method=["DELETE"], function=check_repo))
555 conditions=dict(method=["DELETE"], function=check_repo))
555
556
556 rmap.connect('repo_settings_update', '/{repo_name:.*?}/settings',
557 rmap.connect('repo_settings_update', '/{repo_name:.*?}/settings',
557 controller='settings', action="update",
558 controller='settings', action="update",
558 conditions=dict(method=["PUT"], function=check_repo))
559 conditions=dict(method=["PUT"], function=check_repo))
559
560
560 rmap.connect('repo_settings_home', '/{repo_name:.*?}/settings',
561 rmap.connect('repo_settings_home', '/{repo_name:.*?}/settings',
561 controller='settings', action='index',
562 controller='settings', action='index',
562 conditions=dict(function=check_repo))
563 conditions=dict(function=check_repo))
563
564
565 rmap.connect('toggle_locking', "/{repo_name:.*?}/locking_toggle",
566 controller='settings', action="toggle_locking",
567 conditions=dict(method=["GET"], function=check_repo))
568
564 rmap.connect('repo_fork_create_home', '/{repo_name:.*?}/fork',
569 rmap.connect('repo_fork_create_home', '/{repo_name:.*?}/fork',
565 controller='forks', action='fork_create',
570 controller='forks', action='fork_create',
566 conditions=dict(function=check_repo, method=["POST"]))
571 conditions=dict(function=check_repo, method=["POST"]))
567
572
568 rmap.connect('repo_fork_home', '/{repo_name:.*?}/fork',
573 rmap.connect('repo_fork_home', '/{repo_name:.*?}/fork',
569 controller='forks', action='fork',
574 controller='forks', action='fork',
570 conditions=dict(function=check_repo))
575 conditions=dict(function=check_repo))
571
576
572 rmap.connect('repo_forks_home', '/{repo_name:.*?}/forks',
577 rmap.connect('repo_forks_home', '/{repo_name:.*?}/forks',
573 controller='forks', action='forks',
578 controller='forks', action='forks',
574 conditions=dict(function=check_repo))
579 conditions=dict(function=check_repo))
575
580
576 rmap.connect('repo_followers_home', '/{repo_name:.*?}/followers',
581 rmap.connect('repo_followers_home', '/{repo_name:.*?}/followers',
577 controller='followers', action='followers',
582 controller='followers', action='followers',
578 conditions=dict(function=check_repo))
583 conditions=dict(function=check_repo))
579
584
580 return rmap
585 return rmap
@@ -1,59 +1,60 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.admin.admin
3 rhodecode.controllers.admin.admin
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Controller for Admin panel of Rhodecode
6 Controller for Admin panel of Rhodecode
7
7
8 :created_on: Apr 7, 2010
8 :created_on: Apr 7, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 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
27
28 from pylons import request, tmpl_context as c
28 from pylons import request, tmpl_context as c
29 from sqlalchemy.orm import joinedload
29 from sqlalchemy.orm import joinedload
30 from webhelpers.paginate import Page
30 from webhelpers.paginate import Page
31
31
32 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
32 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator
33 from rhodecode.lib.base import BaseController, render
33 from rhodecode.lib.base import BaseController, render
34 from rhodecode.model.db import UserLog
34 from rhodecode.model.db import UserLog
35 from rhodecode.lib.utils2 import safe_int
35
36
36 log = logging.getLogger(__name__)
37 log = logging.getLogger(__name__)
37
38
38
39
39 class AdminController(BaseController):
40 class AdminController(BaseController):
40
41
41 @LoginRequired()
42 @LoginRequired()
42 def __before__(self):
43 def __before__(self):
43 super(AdminController, self).__before__()
44 super(AdminController, self).__before__()
44
45
45 @HasPermissionAllDecorator('hg.admin')
46 @HasPermissionAllDecorator('hg.admin')
46 def index(self):
47 def index(self):
47
48
48 users_log = UserLog.query()\
49 users_log = UserLog.query()\
49 .options(joinedload(UserLog.user))\
50 .options(joinedload(UserLog.user))\
50 .options(joinedload(UserLog.repository))\
51 .options(joinedload(UserLog.repository))\
51 .order_by(UserLog.action_date.desc())
52 .order_by(UserLog.action_date.desc())
52
53
53 p = int(request.params.get('page', 1))
54 p = safe_int(request.params.get('page', 1), 1)
54 c.users_log = Page(users_log, page=p, items_per_page=10)
55 c.users_log = Page(users_log, page=p, items_per_page=10)
55 c.log_data = render('admin/admin_log.html')
56 c.log_data = render('admin/admin_log.html')
56
57
57 if request.environ.get('HTTP_X_PARTIAL_XHR'):
58 if request.environ.get('HTTP_X_PARTIAL_XHR'):
58 return c.log_data
59 return c.log_data
59 return render('admin/admin.html')
60 return render('admin/admin.html')
@@ -1,170 +1,172 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.admin.notifications
3 rhodecode.controllers.admin.notifications
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 notifications controller for RhodeCode
6 notifications controller for RhodeCode
7
7
8 :created_on: Nov 23, 2010
8 :created_on: Nov 23, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 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
28
29 from pylons import request
29 from pylons import request
30 from pylons import tmpl_context as c, url
30 from pylons import tmpl_context as c, url
31 from pylons.controllers.util import redirect
31 from pylons.controllers.util import redirect
32
32
33 from webhelpers.paginate import Page
33 from webhelpers.paginate import Page
34
34
35 from rhodecode.lib.base import BaseController, render
35 from rhodecode.lib.base import BaseController, render
36 from rhodecode.model.db import Notification
36 from rhodecode.model.db import Notification
37
37
38 from rhodecode.model.notification import NotificationModel
38 from rhodecode.model.notification import NotificationModel
39 from rhodecode.lib.auth import LoginRequired, NotAnonymous
39 from rhodecode.lib.auth import LoginRequired, NotAnonymous
40 from rhodecode.lib import helpers as h
40 from rhodecode.lib import helpers as h
41 from rhodecode.model.meta import Session
41 from rhodecode.model.meta import Session
42 from rhodecode.lib.utils2 import safe_int
42
43
43
44
44 log = logging.getLogger(__name__)
45 log = logging.getLogger(__name__)
45
46
46
47
47 class NotificationsController(BaseController):
48 class NotificationsController(BaseController):
48 """REST Controller styled on the Atom Publishing Protocol"""
49 """REST Controller styled on the Atom Publishing Protocol"""
49 # To properly map this controller, ensure your config/routing.py
50 # To properly map this controller, ensure your config/routing.py
50 # file has a resource setup:
51 # file has a resource setup:
51 # map.resource('notification', 'notifications', controller='_admin/notifications',
52 # map.resource('notification', 'notifications', controller='_admin/notifications',
52 # path_prefix='/_admin', name_prefix='_admin_')
53 # path_prefix='/_admin', name_prefix='_admin_')
53
54
54 @LoginRequired()
55 @LoginRequired()
55 @NotAnonymous()
56 @NotAnonymous()
56 def __before__(self):
57 def __before__(self):
57 super(NotificationsController, self).__before__()
58 super(NotificationsController, self).__before__()
58
59
59 def index(self, format='html'):
60 def index(self, format='html'):
60 """GET /_admin/notifications: All items in the collection"""
61 """GET /_admin/notifications: All items in the collection"""
61 # url('notifications')
62 # url('notifications')
62 c.user = self.rhodecode_user
63 c.user = self.rhodecode_user
63 notif = NotificationModel().get_for_user(self.rhodecode_user.user_id,
64 notif = NotificationModel().get_for_user(self.rhodecode_user.user_id,
64 filter_=request.GET.getall('type'))
65 filter_=request.GET.getall('type'))
65 p = int(request.params.get('page', 1))
66
67 p = safe_int(request.params.get('page', 1), 1)
66 c.notifications = Page(notif, page=p, items_per_page=10)
68 c.notifications = Page(notif, page=p, items_per_page=10)
67 c.pull_request_type = Notification.TYPE_PULL_REQUEST
69 c.pull_request_type = Notification.TYPE_PULL_REQUEST
68 c.comment_type = [Notification.TYPE_CHANGESET_COMMENT,
70 c.comment_type = [Notification.TYPE_CHANGESET_COMMENT,
69 Notification.TYPE_PULL_REQUEST_COMMENT]
71 Notification.TYPE_PULL_REQUEST_COMMENT]
70
72
71 _current_filter = request.GET.getall('type')
73 _current_filter = request.GET.getall('type')
72 c.current_filter = 'all'
74 c.current_filter = 'all'
73 if _current_filter == [c.pull_request_type]:
75 if _current_filter == [c.pull_request_type]:
74 c.current_filter = 'pull_request'
76 c.current_filter = 'pull_request'
75 elif _current_filter == c.comment_type:
77 elif _current_filter == c.comment_type:
76 c.current_filter = 'comment'
78 c.current_filter = 'comment'
77
79
78 return render('admin/notifications/notifications.html')
80 return render('admin/notifications/notifications.html')
79
81
80 def mark_all_read(self):
82 def mark_all_read(self):
81 if request.environ.get('HTTP_X_PARTIAL_XHR'):
83 if request.environ.get('HTTP_X_PARTIAL_XHR'):
82 nm = NotificationModel()
84 nm = NotificationModel()
83 # mark all read
85 # mark all read
84 nm.mark_all_read_for_user(self.rhodecode_user.user_id,
86 nm.mark_all_read_for_user(self.rhodecode_user.user_id,
85 filter_=request.GET.getall('type'))
87 filter_=request.GET.getall('type'))
86 Session().commit()
88 Session().commit()
87 c.user = self.rhodecode_user
89 c.user = self.rhodecode_user
88 notif = nm.get_for_user(self.rhodecode_user.user_id,
90 notif = nm.get_for_user(self.rhodecode_user.user_id,
89 filter_=request.GET.getall('type'))
91 filter_=request.GET.getall('type'))
90 c.notifications = Page(notif, page=1, items_per_page=10)
92 c.notifications = Page(notif, page=1, items_per_page=10)
91 return render('admin/notifications/notifications_data.html')
93 return render('admin/notifications/notifications_data.html')
92
94
93 def create(self):
95 def create(self):
94 """POST /_admin/notifications: Create a new item"""
96 """POST /_admin/notifications: Create a new item"""
95 # url('notifications')
97 # url('notifications')
96
98
97 def new(self, format='html'):
99 def new(self, format='html'):
98 """GET /_admin/notifications/new: Form to create a new item"""
100 """GET /_admin/notifications/new: Form to create a new item"""
99 # url('new_notification')
101 # url('new_notification')
100
102
101 def update(self, notification_id):
103 def update(self, notification_id):
102 """PUT /_admin/notifications/id: Update an existing item"""
104 """PUT /_admin/notifications/id: Update an existing item"""
103 # Forms posted to this method should contain a hidden field:
105 # Forms posted to this method should contain a hidden field:
104 # <input type="hidden" name="_method" value="PUT" />
106 # <input type="hidden" name="_method" value="PUT" />
105 # Or using helpers:
107 # Or using helpers:
106 # h.form(url('notification', notification_id=ID),
108 # h.form(url('notification', notification_id=ID),
107 # method='put')
109 # method='put')
108 # url('notification', notification_id=ID)
110 # url('notification', notification_id=ID)
109 try:
111 try:
110 no = Notification.get(notification_id)
112 no = Notification.get(notification_id)
111 owner = lambda: (no.notifications_to_users.user.user_id
113 owner = lambda: (no.notifications_to_users.user.user_id
112 == c.rhodecode_user.user_id)
114 == c.rhodecode_user.user_id)
113 if h.HasPermissionAny('hg.admin')() or owner:
115 if h.HasPermissionAny('hg.admin')() or owner:
114 NotificationModel().mark_read(c.rhodecode_user.user_id, no)
116 NotificationModel().mark_read(c.rhodecode_user.user_id, no)
115 Session().commit()
117 Session().commit()
116 return 'ok'
118 return 'ok'
117 except Exception:
119 except Exception:
118 Session.rollback()
120 Session.rollback()
119 log.error(traceback.format_exc())
121 log.error(traceback.format_exc())
120 return 'fail'
122 return 'fail'
121
123
122 def delete(self, notification_id):
124 def delete(self, notification_id):
123 """DELETE /_admin/notifications/id: Delete an existing item"""
125 """DELETE /_admin/notifications/id: Delete an existing item"""
124 # Forms posted to this method should contain a hidden field:
126 # Forms posted to this method should contain a hidden field:
125 # <input type="hidden" name="_method" value="DELETE" />
127 # <input type="hidden" name="_method" value="DELETE" />
126 # Or using helpers:
128 # Or using helpers:
127 # h.form(url('notification', notification_id=ID),
129 # h.form(url('notification', notification_id=ID),
128 # method='delete')
130 # method='delete')
129 # url('notification', notification_id=ID)
131 # url('notification', notification_id=ID)
130
132
131 try:
133 try:
132 no = Notification.get(notification_id)
134 no = Notification.get(notification_id)
133 owner = lambda: (no.notifications_to_users.user.user_id
135 owner = lambda: (no.notifications_to_users.user.user_id
134 == c.rhodecode_user.user_id)
136 == c.rhodecode_user.user_id)
135 if h.HasPermissionAny('hg.admin')() or owner:
137 if h.HasPermissionAny('hg.admin')() or owner:
136 NotificationModel().delete(c.rhodecode_user.user_id, no)
138 NotificationModel().delete(c.rhodecode_user.user_id, no)
137 Session().commit()
139 Session().commit()
138 return 'ok'
140 return 'ok'
139 except Exception:
141 except Exception:
140 Session.rollback()
142 Session.rollback()
141 log.error(traceback.format_exc())
143 log.error(traceback.format_exc())
142 return 'fail'
144 return 'fail'
143
145
144 def show(self, notification_id, format='html'):
146 def show(self, notification_id, format='html'):
145 """GET /_admin/notifications/id: Show a specific item"""
147 """GET /_admin/notifications/id: Show a specific item"""
146 # url('notification', notification_id=ID)
148 # url('notification', notification_id=ID)
147 c.user = self.rhodecode_user
149 c.user = self.rhodecode_user
148 no = Notification.get(notification_id)
150 no = Notification.get(notification_id)
149
151
150 owner = lambda: (no.notifications_to_users.user.user_id
152 owner = lambda: (no.notifications_to_users.user.user_id
151 == c.user.user_id)
153 == c.user.user_id)
152 if no and (h.HasPermissionAny('hg.admin', 'repository.admin')() or owner):
154 if no and (h.HasPermissionAny('hg.admin', 'repository.admin')() or owner):
153 unotification = NotificationModel()\
155 unotification = NotificationModel()\
154 .get_user_notification(c.user.user_id, no)
156 .get_user_notification(c.user.user_id, no)
155
157
156 # if this association to user is not valid, we don't want to show
158 # if this association to user is not valid, we don't want to show
157 # this message
159 # this message
158 if unotification:
160 if unotification:
159 if unotification.read is False:
161 if unotification.read is False:
160 unotification.mark_as_read()
162 unotification.mark_as_read()
161 Session().commit()
163 Session().commit()
162 c.notification = no
164 c.notification = no
163
165
164 return render('admin/notifications/show_notification.html')
166 return render('admin/notifications/show_notification.html')
165
167
166 return redirect(url('notifications'))
168 return redirect(url('notifications'))
167
169
168 def edit(self, notification_id, format='html'):
170 def edit(self, notification_id, format='html'):
169 """GET /_admin/notifications/id/edit: Form to edit an existing item"""
171 """GET /_admin/notifications/id/edit: Form to edit an existing item"""
170 # url('edit_notification', notification_id=ID)
172 # url('edit_notification', notification_id=ID)
@@ -1,509 +1,509 b''
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 Repositories controller for RhodeCode
6 Repositories 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) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 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 webob.exc import HTTPInternalServerError
31 from webob.exc import HTTPInternalServerError
32 from pylons import request, session, tmpl_context as c, url
32 from pylons import request, session, tmpl_context as c, url
33 from pylons.controllers.util import redirect
33 from pylons.controllers.util import redirect
34 from pylons.i18n.translation import _
34 from pylons.i18n.translation import _
35 from sqlalchemy.exc import IntegrityError
35 from sqlalchemy.exc import IntegrityError
36
36
37 import rhodecode
37 import rhodecode
38 from rhodecode.lib import helpers as h
38 from rhodecode.lib import helpers as h
39 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
39 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
40 HasPermissionAnyDecorator, HasRepoPermissionAllDecorator
40 HasPermissionAnyDecorator, HasRepoPermissionAllDecorator
41 from rhodecode.lib.base import BaseController, render
41 from rhodecode.lib.base import BaseController, render
42 from rhodecode.lib.utils import invalidate_cache, action_logger, repo_name_slug
42 from rhodecode.lib.utils import invalidate_cache, action_logger, repo_name_slug
43 from rhodecode.lib.helpers import get_token
43 from rhodecode.lib.helpers import get_token
44 from rhodecode.model.meta import Session
44 from rhodecode.model.meta import Session
45 from rhodecode.model.db import User, Repository, UserFollowing, RepoGroup
45 from rhodecode.model.db import User, Repository, UserFollowing, RepoGroup
46 from rhodecode.model.forms import RepoForm
46 from rhodecode.model.forms import RepoForm
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.lib.compat import json
49 from rhodecode.lib.compat import json
50
50
51 log = logging.getLogger(__name__)
51 log = logging.getLogger(__name__)
52
52
53
53
54 class ReposController(BaseController):
54 class ReposController(BaseController):
55 """
55 """
56 REST Controller styled on the Atom Publishing Protocol"""
56 REST Controller styled on the Atom Publishing Protocol"""
57 # To properly map this controller, ensure your config/routing.py
57 # To properly map this controller, ensure your config/routing.py
58 # file has a resource setup:
58 # file has a resource setup:
59 # map.resource('repo', 'repos')
59 # map.resource('repo', 'repos')
60
60
61 @LoginRequired()
61 @LoginRequired()
62 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
62 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
63 def __before__(self):
63 def __before__(self):
64 c.admin_user = session.get('admin_user')
64 c.admin_user = session.get('admin_user')
65 c.admin_username = session.get('admin_username')
65 c.admin_username = session.get('admin_username')
66 super(ReposController, self).__before__()
66 super(ReposController, self).__before__()
67
67
68 def __load_defaults(self):
68 def __load_defaults(self):
69 c.repo_groups = RepoGroup.groups_choices()
69 c.repo_groups = RepoGroup.groups_choices(check_perms=True)
70 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
70 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
71
71
72 repo_model = RepoModel()
72 repo_model = RepoModel()
73 c.users_array = repo_model.get_users_js()
73 c.users_array = repo_model.get_users_js()
74 c.users_groups_array = repo_model.get_users_groups_js()
74 c.users_groups_array = repo_model.get_users_groups_js()
75 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
75 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
76 c.landing_revs_choices = choices
76 c.landing_revs_choices = choices
77
77
78 def __load_data(self, repo_name=None):
78 def __load_data(self, repo_name=None):
79 """
79 """
80 Load defaults settings for edit, and update
80 Load defaults settings for edit, and update
81
81
82 :param repo_name:
82 :param repo_name:
83 """
83 """
84 self.__load_defaults()
84 self.__load_defaults()
85
85
86 c.repo_info = db_repo = Repository.get_by_repo_name(repo_name)
86 c.repo_info = db_repo = Repository.get_by_repo_name(repo_name)
87 repo = db_repo.scm_instance
87 repo = db_repo.scm_instance
88
88
89 if c.repo_info is None:
89 if c.repo_info is None:
90 h.flash(_('%s repository is not mapped to db perhaps'
90 h.flash(_('%s repository is not mapped to db perhaps'
91 ' it was created or renamed from the filesystem'
91 ' it was created or renamed from the filesystem'
92 ' please run the application again'
92 ' please run the application again'
93 ' in order to rescan repositories') % repo_name,
93 ' in order to rescan repositories') % repo_name,
94 category='error')
94 category='error')
95
95
96 return redirect(url('repos'))
96 return redirect(url('repos'))
97
97
98 choices, c.landing_revs = ScmModel().get_repo_landing_revs(c.repo_info)
98 choices, c.landing_revs = ScmModel().get_repo_landing_revs(c.repo_info)
99 c.landing_revs_choices = choices
99 c.landing_revs_choices = choices
100
100
101 c.default_user_id = User.get_by_username('default').user_id
101 c.default_user_id = User.get_by_username('default').user_id
102 c.in_public_journal = UserFollowing.query()\
102 c.in_public_journal = UserFollowing.query()\
103 .filter(UserFollowing.user_id == c.default_user_id)\
103 .filter(UserFollowing.user_id == c.default_user_id)\
104 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
104 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
105
105
106 if c.repo_info.stats:
106 if c.repo_info.stats:
107 # this is on what revision we ended up so we add +1 for count
107 # this is on what revision we ended up so we add +1 for count
108 last_rev = c.repo_info.stats.stat_on_revision + 1
108 last_rev = c.repo_info.stats.stat_on_revision + 1
109 else:
109 else:
110 last_rev = 0
110 last_rev = 0
111 c.stats_revision = last_rev
111 c.stats_revision = last_rev
112
112
113 c.repo_last_rev = repo.count() if repo.revisions else 0
113 c.repo_last_rev = repo.count() if repo.revisions else 0
114
114
115 if last_rev == 0 or c.repo_last_rev == 0:
115 if last_rev == 0 or c.repo_last_rev == 0:
116 c.stats_percentage = 0
116 c.stats_percentage = 0
117 else:
117 else:
118 c.stats_percentage = '%.2f' % ((float((last_rev)) /
118 c.stats_percentage = '%.2f' % ((float((last_rev)) /
119 c.repo_last_rev) * 100)
119 c.repo_last_rev) * 100)
120
120
121 defaults = RepoModel()._get_defaults(repo_name)
121 defaults = RepoModel()._get_defaults(repo_name)
122
122
123 c.repos_list = [('', _('--REMOVE FORK--'))]
123 c.repos_list = [('', _('--REMOVE FORK--'))]
124 c.repos_list += [(x.repo_id, x.repo_name) for x in
124 c.repos_list += [(x.repo_id, x.repo_name) for x in
125 Repository.query().order_by(Repository.repo_name).all()
125 Repository.query().order_by(Repository.repo_name).all()
126 if x.repo_id != c.repo_info.repo_id]
126 if x.repo_id != c.repo_info.repo_id]
127
127
128 defaults['id_fork_of'] = db_repo.fork.repo_id if db_repo.fork else ''
128 defaults['id_fork_of'] = db_repo.fork.repo_id if db_repo.fork else ''
129 return defaults
129 return defaults
130
130
131 @HasPermissionAllDecorator('hg.admin')
131 @HasPermissionAllDecorator('hg.admin')
132 def index(self, format='html'):
132 def index(self, format='html'):
133 """GET /repos: All items in the collection"""
133 """GET /repos: All items in the collection"""
134 # url('repos')
134 # url('repos')
135
135
136 c.repos_list = Repository.query()\
136 c.repos_list = Repository.query()\
137 .order_by(Repository.repo_name)\
137 .order_by(Repository.repo_name)\
138 .all()
138 .all()
139
139
140 repos_data = []
140 repos_data = []
141 total_records = len(c.repos_list)
141 total_records = len(c.repos_list)
142
142
143 _tmpl_lookup = rhodecode.CONFIG['pylons.app_globals'].mako_lookup
143 _tmpl_lookup = rhodecode.CONFIG['pylons.app_globals'].mako_lookup
144 template = _tmpl_lookup.get_template('data_table/_dt_elements.html')
144 template = _tmpl_lookup.get_template('data_table/_dt_elements.html')
145
145
146 quick_menu = lambda repo_name: (template.get_def("quick_menu")
146 quick_menu = lambda repo_name: (template.get_def("quick_menu")
147 .render(repo_name, _=_, h=h, c=c))
147 .render(repo_name, _=_, h=h, c=c))
148 repo_lnk = lambda name, rtype, private, fork_of: (
148 repo_lnk = lambda name, rtype, private, fork_of: (
149 template.get_def("repo_name")
149 template.get_def("repo_name")
150 .render(name, rtype, private, fork_of, short_name=False,
150 .render(name, rtype, private, fork_of, short_name=False,
151 admin=True, _=_, h=h, c=c))
151 admin=True, _=_, h=h, c=c))
152
152
153 repo_actions = lambda repo_name: (template.get_def("repo_actions")
153 repo_actions = lambda repo_name: (template.get_def("repo_actions")
154 .render(repo_name, _=_, h=h, c=c))
154 .render(repo_name, _=_, h=h, c=c))
155
155
156 for repo in c.repos_list:
156 for repo in c.repos_list:
157 repos_data.append({
157 repos_data.append({
158 "menu": quick_menu(repo.repo_name),
158 "menu": quick_menu(repo.repo_name),
159 "raw_name": repo.repo_name,
159 "raw_name": repo.repo_name,
160 "name": repo_lnk(repo.repo_name, repo.repo_type,
160 "name": repo_lnk(repo.repo_name, repo.repo_type,
161 repo.private, repo.fork),
161 repo.private, repo.fork),
162 "desc": repo.description,
162 "desc": repo.description,
163 "owner": repo.user.username,
163 "owner": repo.user.username,
164 "action": repo_actions(repo.repo_name),
164 "action": repo_actions(repo.repo_name),
165 })
165 })
166
166
167 c.data = json.dumps({
167 c.data = json.dumps({
168 "totalRecords": total_records,
168 "totalRecords": total_records,
169 "startIndex": 0,
169 "startIndex": 0,
170 "sort": "name",
170 "sort": "name",
171 "dir": "asc",
171 "dir": "asc",
172 "records": repos_data
172 "records": repos_data
173 })
173 })
174
174
175 return render('admin/repos/repos.html')
175 return render('admin/repos/repos.html')
176
176
177 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
177 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
178 def create(self):
178 def create(self):
179 """
179 """
180 POST /repos: Create a new item"""
180 POST /repos: Create a new item"""
181 # url('repos')
181 # url('repos')
182
182
183 self.__load_defaults()
183 self.__load_defaults()
184 form_result = {}
184 form_result = {}
185 try:
185 try:
186 form_result = RepoForm(repo_groups=c.repo_groups_choices,
186 form_result = RepoForm(repo_groups=c.repo_groups_choices,
187 landing_revs=c.landing_revs_choices)()\
187 landing_revs=c.landing_revs_choices)()\
188 .to_python(dict(request.POST))
188 .to_python(dict(request.POST))
189 new_repo = RepoModel().create(form_result,
189 new_repo = RepoModel().create(form_result,
190 self.rhodecode_user.user_id)
190 self.rhodecode_user.user_id)
191 if form_result['clone_uri']:
191 if form_result['clone_uri']:
192 h.flash(_('created repository %s from %s') \
192 h.flash(_('created repository %s from %s') \
193 % (form_result['repo_name'], form_result['clone_uri']),
193 % (form_result['repo_name'], form_result['clone_uri']),
194 category='success')
194 category='success')
195 else:
195 else:
196 h.flash(_('created repository %s') % form_result['repo_name'],
196 h.flash(_('created repository %s') % form_result['repo_name'],
197 category='success')
197 category='success')
198
198
199 if request.POST.get('user_created'):
199 if request.POST.get('user_created'):
200 # created by regular non admin user
200 # created by regular non admin user
201 action_logger(self.rhodecode_user, 'user_created_repo',
201 action_logger(self.rhodecode_user, 'user_created_repo',
202 form_result['repo_name_full'], self.ip_addr,
202 form_result['repo_name_full'], self.ip_addr,
203 self.sa)
203 self.sa)
204 else:
204 else:
205 action_logger(self.rhodecode_user, 'admin_created_repo',
205 action_logger(self.rhodecode_user, 'admin_created_repo',
206 form_result['repo_name_full'], self.ip_addr,
206 form_result['repo_name_full'], self.ip_addr,
207 self.sa)
207 self.sa)
208 Session().commit()
208 Session().commit()
209 except formencode.Invalid, errors:
209 except formencode.Invalid, errors:
210
210
211 c.new_repo = errors.value['repo_name']
211 c.new_repo = errors.value['repo_name']
212
212
213 if request.POST.get('user_created'):
213 if request.POST.get('user_created'):
214 r = render('admin/repos/repo_add_create_repository.html')
214 r = render('admin/repos/repo_add_create_repository.html')
215 else:
215 else:
216 r = render('admin/repos/repo_add.html')
216 r = render('admin/repos/repo_add.html')
217
217
218 return htmlfill.render(
218 return htmlfill.render(
219 r,
219 r,
220 defaults=errors.value,
220 defaults=errors.value,
221 errors=errors.error_dict or {},
221 errors=errors.error_dict or {},
222 prefix_error=False,
222 prefix_error=False,
223 encoding="UTF-8")
223 encoding="UTF-8")
224
224
225 except Exception:
225 except Exception:
226 log.error(traceback.format_exc())
226 log.error(traceback.format_exc())
227 msg = _('error occurred during creation of repository %s') \
227 msg = _('error occurred during creation of repository %s') \
228 % form_result.get('repo_name')
228 % form_result.get('repo_name')
229 h.flash(msg, category='error')
229 h.flash(msg, category='error')
230 return redirect(url('repos'))
230 return redirect(url('repos'))
231 #redirect to our new repo !
231 #redirect to our new repo !
232 return redirect(url('summary_home', repo_name=new_repo.repo_name))
232 return redirect(url('summary_home', repo_name=new_repo.repo_name))
233
233
234 @HasPermissionAllDecorator('hg.admin')
234 @HasPermissionAllDecorator('hg.admin')
235 def new(self, format='html'):
235 def new(self, format='html'):
236 """GET /repos/new: Form to create a new item"""
236 """GET /repos/new: Form to create a new item"""
237 new_repo = request.GET.get('repo', '')
237 new_repo = request.GET.get('repo', '')
238 c.new_repo = repo_name_slug(new_repo)
238 c.new_repo = repo_name_slug(new_repo)
239 self.__load_defaults()
239 self.__load_defaults()
240 return render('admin/repos/repo_add.html')
240 return render('admin/repos/repo_add.html')
241
241
242 @HasPermissionAllDecorator('hg.admin')
242 @HasPermissionAllDecorator('hg.admin')
243 def update(self, repo_name):
243 def update(self, repo_name):
244 """
244 """
245 PUT /repos/repo_name: Update an existing item"""
245 PUT /repos/repo_name: Update an existing item"""
246 # Forms posted to this method should contain a hidden field:
246 # Forms posted to this method should contain a hidden field:
247 # <input type="hidden" name="_method" value="PUT" />
247 # <input type="hidden" name="_method" value="PUT" />
248 # Or using helpers:
248 # Or using helpers:
249 # h.form(url('repo', repo_name=ID),
249 # h.form(url('repo', repo_name=ID),
250 # method='put')
250 # method='put')
251 # url('repo', repo_name=ID)
251 # url('repo', repo_name=ID)
252 self.__load_defaults()
252 self.__load_defaults()
253 repo_model = RepoModel()
253 repo_model = RepoModel()
254 changed_name = repo_name
254 changed_name = repo_name
255 #override the choices with extracted revisions !
255 #override the choices with extracted revisions !
256 choices, c.landing_revs = ScmModel().get_repo_landing_revs(repo_name)
256 choices, c.landing_revs = ScmModel().get_repo_landing_revs(repo_name)
257 c.landing_revs_choices = choices
257 c.landing_revs_choices = choices
258
258
259 _form = RepoForm(edit=True, old_data={'repo_name': repo_name},
259 _form = RepoForm(edit=True, old_data={'repo_name': repo_name},
260 repo_groups=c.repo_groups_choices,
260 repo_groups=c.repo_groups_choices,
261 landing_revs=c.landing_revs_choices)()
261 landing_revs=c.landing_revs_choices)()
262 try:
262 try:
263 form_result = _form.to_python(dict(request.POST))
263 form_result = _form.to_python(dict(request.POST))
264 repo = repo_model.update(repo_name, form_result)
264 repo = repo_model.update(repo_name, form_result)
265 invalidate_cache('get_repo_cached_%s' % repo_name)
265 invalidate_cache('get_repo_cached_%s' % repo_name)
266 h.flash(_('Repository %s updated successfully') % repo_name,
266 h.flash(_('Repository %s updated successfully') % repo_name,
267 category='success')
267 category='success')
268 changed_name = repo.repo_name
268 changed_name = repo.repo_name
269 action_logger(self.rhodecode_user, 'admin_updated_repo',
269 action_logger(self.rhodecode_user, 'admin_updated_repo',
270 changed_name, self.ip_addr, self.sa)
270 changed_name, self.ip_addr, self.sa)
271 Session().commit()
271 Session().commit()
272 except formencode.Invalid, errors:
272 except formencode.Invalid, errors:
273 defaults = self.__load_data(repo_name)
273 defaults = self.__load_data(repo_name)
274 defaults.update(errors.value)
274 defaults.update(errors.value)
275 return htmlfill.render(
275 return htmlfill.render(
276 render('admin/repos/repo_edit.html'),
276 render('admin/repos/repo_edit.html'),
277 defaults=defaults,
277 defaults=defaults,
278 errors=errors.error_dict or {},
278 errors=errors.error_dict or {},
279 prefix_error=False,
279 prefix_error=False,
280 encoding="UTF-8")
280 encoding="UTF-8")
281
281
282 except Exception:
282 except Exception:
283 log.error(traceback.format_exc())
283 log.error(traceback.format_exc())
284 h.flash(_('error occurred during update of repository %s') \
284 h.flash(_('error occurred during update of repository %s') \
285 % repo_name, category='error')
285 % repo_name, category='error')
286 return redirect(url('edit_repo', repo_name=changed_name))
286 return redirect(url('edit_repo', repo_name=changed_name))
287
287
288 @HasPermissionAllDecorator('hg.admin')
288 @HasPermissionAllDecorator('hg.admin')
289 def delete(self, repo_name):
289 def delete(self, repo_name):
290 """
290 """
291 DELETE /repos/repo_name: Delete an existing item"""
291 DELETE /repos/repo_name: Delete an existing item"""
292 # Forms posted to this method should contain a hidden field:
292 # Forms posted to this method should contain a hidden field:
293 # <input type="hidden" name="_method" value="DELETE" />
293 # <input type="hidden" name="_method" value="DELETE" />
294 # Or using helpers:
294 # Or using helpers:
295 # h.form(url('repo', repo_name=ID),
295 # h.form(url('repo', repo_name=ID),
296 # method='delete')
296 # method='delete')
297 # url('repo', repo_name=ID)
297 # url('repo', repo_name=ID)
298
298
299 repo_model = RepoModel()
299 repo_model = RepoModel()
300 repo = repo_model.get_by_repo_name(repo_name)
300 repo = repo_model.get_by_repo_name(repo_name)
301 if not repo:
301 if not repo:
302 h.flash(_('%s repository is not mapped to db perhaps'
302 h.flash(_('%s repository is not mapped to db perhaps'
303 ' it was moved or renamed from the filesystem'
303 ' it was moved or renamed from the filesystem'
304 ' please run the application again'
304 ' please run the application again'
305 ' in order to rescan repositories') % repo_name,
305 ' in order to rescan repositories') % repo_name,
306 category='error')
306 category='error')
307
307
308 return redirect(url('repos'))
308 return redirect(url('repos'))
309 try:
309 try:
310 action_logger(self.rhodecode_user, 'admin_deleted_repo',
310 action_logger(self.rhodecode_user, 'admin_deleted_repo',
311 repo_name, self.ip_addr, self.sa)
311 repo_name, self.ip_addr, self.sa)
312 repo_model.delete(repo)
312 repo_model.delete(repo)
313 invalidate_cache('get_repo_cached_%s' % repo_name)
313 invalidate_cache('get_repo_cached_%s' % repo_name)
314 h.flash(_('deleted repository %s') % repo_name, category='success')
314 h.flash(_('deleted repository %s') % repo_name, category='success')
315 Session().commit()
315 Session().commit()
316 except IntegrityError, e:
316 except IntegrityError, e:
317 if e.message.find('repositories_fork_id_fkey') != -1:
317 if e.message.find('repositories_fork_id_fkey') != -1:
318 log.error(traceback.format_exc())
318 log.error(traceback.format_exc())
319 h.flash(_('Cannot delete %s it still contains attached '
319 h.flash(_('Cannot delete %s it still contains attached '
320 'forks') % repo_name,
320 'forks') % repo_name,
321 category='warning')
321 category='warning')
322 else:
322 else:
323 log.error(traceback.format_exc())
323 log.error(traceback.format_exc())
324 h.flash(_('An error occurred during '
324 h.flash(_('An error occurred during '
325 'deletion of %s') % repo_name,
325 'deletion of %s') % repo_name,
326 category='error')
326 category='error')
327
327
328 except Exception, e:
328 except Exception, e:
329 log.error(traceback.format_exc())
329 log.error(traceback.format_exc())
330 h.flash(_('An error occurred during deletion of %s') % repo_name,
330 h.flash(_('An error occurred during deletion of %s') % repo_name,
331 category='error')
331 category='error')
332
332
333 return redirect(url('repos'))
333 return redirect(url('repos'))
334
334
335 @HasRepoPermissionAllDecorator('repository.admin')
335 @HasRepoPermissionAllDecorator('repository.admin')
336 def delete_perm_user(self, repo_name):
336 def delete_perm_user(self, repo_name):
337 """
337 """
338 DELETE an existing repository permission user
338 DELETE an existing repository permission user
339
339
340 :param repo_name:
340 :param repo_name:
341 """
341 """
342 try:
342 try:
343 RepoModel().revoke_user_permission(repo=repo_name,
343 RepoModel().revoke_user_permission(repo=repo_name,
344 user=request.POST['user_id'])
344 user=request.POST['user_id'])
345 Session().commit()
345 Session().commit()
346 except Exception:
346 except Exception:
347 log.error(traceback.format_exc())
347 log.error(traceback.format_exc())
348 h.flash(_('An error occurred during deletion of repository user'),
348 h.flash(_('An error occurred during deletion of repository user'),
349 category='error')
349 category='error')
350 raise HTTPInternalServerError()
350 raise HTTPInternalServerError()
351
351
352 @HasRepoPermissionAllDecorator('repository.admin')
352 @HasRepoPermissionAllDecorator('repository.admin')
353 def delete_perm_users_group(self, repo_name):
353 def delete_perm_users_group(self, repo_name):
354 """
354 """
355 DELETE an existing repository permission users group
355 DELETE an existing repository permission users group
356
356
357 :param repo_name:
357 :param repo_name:
358 """
358 """
359
359
360 try:
360 try:
361 RepoModel().revoke_users_group_permission(
361 RepoModel().revoke_users_group_permission(
362 repo=repo_name, group_name=request.POST['users_group_id']
362 repo=repo_name, group_name=request.POST['users_group_id']
363 )
363 )
364 Session().commit()
364 Session().commit()
365 except Exception:
365 except Exception:
366 log.error(traceback.format_exc())
366 log.error(traceback.format_exc())
367 h.flash(_('An error occurred during deletion of repository'
367 h.flash(_('An error occurred during deletion of repository'
368 ' users groups'),
368 ' users groups'),
369 category='error')
369 category='error')
370 raise HTTPInternalServerError()
370 raise HTTPInternalServerError()
371
371
372 @HasPermissionAllDecorator('hg.admin')
372 @HasPermissionAllDecorator('hg.admin')
373 def repo_stats(self, repo_name):
373 def repo_stats(self, repo_name):
374 """
374 """
375 DELETE an existing repository statistics
375 DELETE an existing repository statistics
376
376
377 :param repo_name:
377 :param repo_name:
378 """
378 """
379
379
380 try:
380 try:
381 RepoModel().delete_stats(repo_name)
381 RepoModel().delete_stats(repo_name)
382 Session().commit()
382 Session().commit()
383 except Exception, e:
383 except Exception, e:
384 log.error(traceback.format_exc())
384 log.error(traceback.format_exc())
385 h.flash(_('An error occurred during deletion of repository stats'),
385 h.flash(_('An error occurred during deletion of repository stats'),
386 category='error')
386 category='error')
387 return redirect(url('edit_repo', repo_name=repo_name))
387 return redirect(url('edit_repo', repo_name=repo_name))
388
388
389 @HasPermissionAllDecorator('hg.admin')
389 @HasPermissionAllDecorator('hg.admin')
390 def repo_cache(self, repo_name):
390 def repo_cache(self, repo_name):
391 """
391 """
392 INVALIDATE existing repository cache
392 INVALIDATE existing repository cache
393
393
394 :param repo_name:
394 :param repo_name:
395 """
395 """
396
396
397 try:
397 try:
398 ScmModel().mark_for_invalidation(repo_name)
398 ScmModel().mark_for_invalidation(repo_name)
399 Session().commit()
399 Session().commit()
400 except Exception, e:
400 except Exception, e:
401 log.error(traceback.format_exc())
401 log.error(traceback.format_exc())
402 h.flash(_('An error occurred during cache invalidation'),
402 h.flash(_('An error occurred during cache invalidation'),
403 category='error')
403 category='error')
404 return redirect(url('edit_repo', repo_name=repo_name))
404 return redirect(url('edit_repo', repo_name=repo_name))
405
405
406 @HasPermissionAllDecorator('hg.admin')
406 @HasPermissionAllDecorator('hg.admin')
407 def repo_locking(self, repo_name):
407 def repo_locking(self, repo_name):
408 """
408 """
409 Unlock repository when it is locked !
409 Unlock repository when it is locked !
410
410
411 :param repo_name:
411 :param repo_name:
412 """
412 """
413
413
414 try:
414 try:
415 repo = Repository.get_by_repo_name(repo_name)
415 repo = Repository.get_by_repo_name(repo_name)
416 if request.POST.get('set_lock'):
416 if request.POST.get('set_lock'):
417 Repository.lock(repo, c.rhodecode_user.user_id)
417 Repository.lock(repo, c.rhodecode_user.user_id)
418 elif request.POST.get('set_unlock'):
418 elif request.POST.get('set_unlock'):
419 Repository.unlock(repo)
419 Repository.unlock(repo)
420 except Exception, e:
420 except Exception, e:
421 log.error(traceback.format_exc())
421 log.error(traceback.format_exc())
422 h.flash(_('An error occurred during unlocking'),
422 h.flash(_('An error occurred during unlocking'),
423 category='error')
423 category='error')
424 return redirect(url('edit_repo', repo_name=repo_name))
424 return redirect(url('edit_repo', repo_name=repo_name))
425
425
426 @HasPermissionAllDecorator('hg.admin')
426 @HasPermissionAllDecorator('hg.admin')
427 def repo_public_journal(self, repo_name):
427 def repo_public_journal(self, repo_name):
428 """
428 """
429 Set's this repository to be visible in public journal,
429 Set's this repository to be visible in public journal,
430 in other words assing default user to follow this repo
430 in other words assing default user to follow this repo
431
431
432 :param repo_name:
432 :param repo_name:
433 """
433 """
434
434
435 cur_token = request.POST.get('auth_token')
435 cur_token = request.POST.get('auth_token')
436 token = get_token()
436 token = get_token()
437 if cur_token == token:
437 if cur_token == token:
438 try:
438 try:
439 repo_id = Repository.get_by_repo_name(repo_name).repo_id
439 repo_id = Repository.get_by_repo_name(repo_name).repo_id
440 user_id = User.get_by_username('default').user_id
440 user_id = User.get_by_username('default').user_id
441 self.scm_model.toggle_following_repo(repo_id, user_id)
441 self.scm_model.toggle_following_repo(repo_id, user_id)
442 h.flash(_('Updated repository visibility in public journal'),
442 h.flash(_('Updated repository visibility in public journal'),
443 category='success')
443 category='success')
444 Session().commit()
444 Session().commit()
445 except:
445 except:
446 h.flash(_('An error occurred during setting this'
446 h.flash(_('An error occurred during setting this'
447 ' repository in public journal'),
447 ' repository in public journal'),
448 category='error')
448 category='error')
449
449
450 else:
450 else:
451 h.flash(_('Token mismatch'), category='error')
451 h.flash(_('Token mismatch'), category='error')
452 return redirect(url('edit_repo', repo_name=repo_name))
452 return redirect(url('edit_repo', repo_name=repo_name))
453
453
454 @HasPermissionAllDecorator('hg.admin')
454 @HasPermissionAllDecorator('hg.admin')
455 def repo_pull(self, repo_name):
455 def repo_pull(self, repo_name):
456 """
456 """
457 Runs task to update given repository with remote changes,
457 Runs task to update given repository with remote changes,
458 ie. make pull on remote location
458 ie. make pull on remote location
459
459
460 :param repo_name:
460 :param repo_name:
461 """
461 """
462 try:
462 try:
463 ScmModel().pull_changes(repo_name, self.rhodecode_user.username)
463 ScmModel().pull_changes(repo_name, self.rhodecode_user.username)
464 h.flash(_('Pulled from remote location'), category='success')
464 h.flash(_('Pulled from remote location'), category='success')
465 except Exception, e:
465 except Exception, e:
466 h.flash(_('An error occurred during pull from remote location'),
466 h.flash(_('An error occurred during pull from remote location'),
467 category='error')
467 category='error')
468
468
469 return redirect(url('edit_repo', repo_name=repo_name))
469 return redirect(url('edit_repo', repo_name=repo_name))
470
470
471 @HasPermissionAllDecorator('hg.admin')
471 @HasPermissionAllDecorator('hg.admin')
472 def repo_as_fork(self, repo_name):
472 def repo_as_fork(self, repo_name):
473 """
473 """
474 Mark given repository as a fork of another
474 Mark given repository as a fork of another
475
475
476 :param repo_name:
476 :param repo_name:
477 """
477 """
478 try:
478 try:
479 fork_id = request.POST.get('id_fork_of')
479 fork_id = request.POST.get('id_fork_of')
480 repo = ScmModel().mark_as_fork(repo_name, fork_id,
480 repo = ScmModel().mark_as_fork(repo_name, fork_id,
481 self.rhodecode_user.username)
481 self.rhodecode_user.username)
482 fork = repo.fork.repo_name if repo.fork else _('Nothing')
482 fork = repo.fork.repo_name if repo.fork else _('Nothing')
483 Session().commit()
483 Session().commit()
484 h.flash(_('Marked repo %s as fork of %s') % (repo_name, fork),
484 h.flash(_('Marked repo %s as fork of %s') % (repo_name, fork),
485 category='success')
485 category='success')
486 except Exception, e:
486 except Exception, e:
487 log.error(traceback.format_exc())
487 log.error(traceback.format_exc())
488 h.flash(_('An error occurred during this operation'),
488 h.flash(_('An error occurred during this operation'),
489 category='error')
489 category='error')
490
490
491 return redirect(url('edit_repo', repo_name=repo_name))
491 return redirect(url('edit_repo', repo_name=repo_name))
492
492
493 @HasPermissionAllDecorator('hg.admin')
493 @HasPermissionAllDecorator('hg.admin')
494 def show(self, repo_name, format='html'):
494 def show(self, repo_name, format='html'):
495 """GET /repos/repo_name: Show a specific item"""
495 """GET /repos/repo_name: Show a specific item"""
496 # url('repo', repo_name=ID)
496 # url('repo', repo_name=ID)
497
497
498 @HasPermissionAllDecorator('hg.admin')
498 @HasPermissionAllDecorator('hg.admin')
499 def edit(self, repo_name, format='html'):
499 def edit(self, repo_name, format='html'):
500 """GET /repos/repo_name/edit: Form to edit an existing item"""
500 """GET /repos/repo_name/edit: Form to edit an existing item"""
501 # url('edit_repo', repo_name=ID)
501 # url('edit_repo', repo_name=ID)
502 defaults = self.__load_data(repo_name)
502 defaults = self.__load_data(repo_name)
503
503
504 return htmlfill.render(
504 return htmlfill.render(
505 render('admin/repos/repo_edit.html'),
505 render('admin/repos/repo_edit.html'),
506 defaults=defaults,
506 defaults=defaults,
507 encoding="UTF-8",
507 encoding="UTF-8",
508 force_defaults=False
508 force_defaults=False
509 )
509 )
@@ -1,317 +1,317 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.admin.repos_groups
3 rhodecode.controllers.admin.repos_groups
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Repositories groups controller for RhodeCode
6 Repositories groups controller for RhodeCode
7
7
8 :created_on: Mar 23, 2010
8 :created_on: Mar 23, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 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 formencode import htmlfill
30 from formencode import htmlfill
31
31
32 from pylons import request, tmpl_context as c, url
32 from pylons import request, tmpl_context as c, url
33 from pylons.controllers.util import redirect
33 from pylons.controllers.util import redirect
34 from pylons.i18n.translation import _
34 from pylons.i18n.translation import _
35
35
36 from sqlalchemy.exc import IntegrityError
36 from sqlalchemy.exc import IntegrityError
37
37
38 from rhodecode.lib import helpers as h
38 from rhodecode.lib import helpers as h
39 from rhodecode.lib.auth import LoginRequired, HasPermissionAnyDecorator,\
39 from rhodecode.lib.auth import LoginRequired, HasPermissionAnyDecorator,\
40 HasReposGroupPermissionAnyDecorator
40 HasReposGroupPermissionAnyDecorator
41 from rhodecode.lib.base import BaseController, render
41 from rhodecode.lib.base import BaseController, render
42 from rhodecode.model.db import RepoGroup
42 from rhodecode.model.db import RepoGroup
43 from rhodecode.model.repos_group import ReposGroupModel
43 from rhodecode.model.repos_group import ReposGroupModel
44 from rhodecode.model.forms import ReposGroupForm
44 from rhodecode.model.forms import ReposGroupForm
45 from rhodecode.model.meta import Session
45 from rhodecode.model.meta import Session
46 from rhodecode.model.repo import RepoModel
46 from rhodecode.model.repo import RepoModel
47 from webob.exc import HTTPInternalServerError, HTTPNotFound
47 from webob.exc import HTTPInternalServerError, HTTPNotFound
48 from rhodecode.lib.utils2 import str2bool
48 from rhodecode.lib.utils2 import str2bool
49
49
50 log = logging.getLogger(__name__)
50 log = logging.getLogger(__name__)
51
51
52
52
53 class ReposGroupsController(BaseController):
53 class ReposGroupsController(BaseController):
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('repos_group', 'repos_groups')
57 # map.resource('repos_group', 'repos_groups')
58
58
59 @LoginRequired()
59 @LoginRequired()
60 def __before__(self):
60 def __before__(self):
61 super(ReposGroupsController, self).__before__()
61 super(ReposGroupsController, self).__before__()
62
62
63 def __load_defaults(self):
63 def __load_defaults(self):
64 c.repo_groups = RepoGroup.groups_choices()
64 c.repo_groups = RepoGroup.groups_choices()
65 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
65 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
66
66
67 repo_model = RepoModel()
67 repo_model = RepoModel()
68 c.users_array = repo_model.get_users_js()
68 c.users_array = repo_model.get_users_js()
69 c.users_groups_array = repo_model.get_users_groups_js()
69 c.users_groups_array = repo_model.get_users_groups_js()
70
70
71 def __load_data(self, group_id):
71 def __load_data(self, group_id):
72 """
72 """
73 Load defaults settings for edit, and update
73 Load defaults settings for edit, and update
74
74
75 :param group_id:
75 :param group_id:
76 """
76 """
77 self.__load_defaults()
77 self.__load_defaults()
78 repo_group = RepoGroup.get_or_404(group_id)
78 repo_group = RepoGroup.get_or_404(group_id)
79 data = repo_group.get_dict()
79 data = repo_group.get_dict()
80 data['group_name'] = repo_group.name
80 data['group_name'] = repo_group.name
81
81
82 # fill repository users
82 # fill repository users
83 for p in repo_group.repo_group_to_perm:
83 for p in repo_group.repo_group_to_perm:
84 data.update({'u_perm_%s' % p.user.username:
84 data.update({'u_perm_%s' % p.user.username:
85 p.permission.permission_name})
85 p.permission.permission_name})
86
86
87 # fill repository groups
87 # fill repository groups
88 for p in repo_group.users_group_to_perm:
88 for p in repo_group.users_group_to_perm:
89 data.update({'g_perm_%s' % p.users_group.users_group_name:
89 data.update({'g_perm_%s' % p.users_group.users_group_name:
90 p.permission.permission_name})
90 p.permission.permission_name})
91
91
92 return data
92 return data
93
93
94 @HasPermissionAnyDecorator('hg.admin')
94 @HasPermissionAnyDecorator('hg.admin')
95 def index(self, format='html'):
95 def index(self, format='html'):
96 """GET /repos_groups: All items in the collection"""
96 """GET /repos_groups: All items in the collection"""
97 # url('repos_groups')
97 # url('repos_groups')
98 sk = lambda g: g.parents[0].group_name if g.parents else g.group_name
98 sk = lambda g: g.parents[0].group_name if g.parents else g.group_name
99 c.groups = sorted(RepoGroup.query().all(), key=sk)
99 c.groups = sorted(RepoGroup.query().all(), key=sk)
100 return render('admin/repos_groups/repos_groups_show.html')
100 return render('admin/repos_groups/repos_groups_show.html')
101
101
102 @HasPermissionAnyDecorator('hg.admin')
102 @HasPermissionAnyDecorator('hg.admin')
103 def create(self):
103 def create(self):
104 """POST /repos_groups: Create a new item"""
104 """POST /repos_groups: Create a new item"""
105 # url('repos_groups')
105 # url('repos_groups')
106 self.__load_defaults()
106 self.__load_defaults()
107 repos_group_form = ReposGroupForm(available_groups =
107 repos_group_form = ReposGroupForm(available_groups =
108 c.repo_groups_choices)()
108 c.repo_groups_choices)()
109 try:
109 try:
110 form_result = repos_group_form.to_python(dict(request.POST))
110 form_result = repos_group_form.to_python(dict(request.POST))
111 ReposGroupModel().create(
111 ReposGroupModel().create(
112 group_name=form_result['group_name'],
112 group_name=form_result['group_name'],
113 group_description=form_result['group_description'],
113 group_description=form_result['group_description'],
114 parent=form_result['group_parent_id']
114 parent=form_result['group_parent_id']
115 )
115 )
116 Session().commit()
116 Session().commit()
117 h.flash(_('created repos group %s') \
117 h.flash(_('created repos group %s') \
118 % form_result['group_name'], category='success')
118 % form_result['group_name'], category='success')
119 #TODO: in futureaction_logger(, '', '', '', self.sa)
119 #TODO: in futureaction_logger(, '', '', '', self.sa)
120 except formencode.Invalid, errors:
120 except formencode.Invalid, errors:
121
121
122 return htmlfill.render(
122 return htmlfill.render(
123 render('admin/repos_groups/repos_groups_add.html'),
123 render('admin/repos_groups/repos_groups_add.html'),
124 defaults=errors.value,
124 defaults=errors.value,
125 errors=errors.error_dict or {},
125 errors=errors.error_dict or {},
126 prefix_error=False,
126 prefix_error=False,
127 encoding="UTF-8")
127 encoding="UTF-8")
128 except Exception:
128 except Exception:
129 log.error(traceback.format_exc())
129 log.error(traceback.format_exc())
130 h.flash(_('error occurred during creation of repos group %s') \
130 h.flash(_('error occurred during creation of repos group %s') \
131 % request.POST.get('group_name'), category='error')
131 % request.POST.get('group_name'), category='error')
132
132
133 return redirect(url('repos_groups'))
133 return redirect(url('repos_groups'))
134
134
135 @HasPermissionAnyDecorator('hg.admin')
135 @HasPermissionAnyDecorator('hg.admin')
136 def new(self, format='html'):
136 def new(self, format='html'):
137 """GET /repos_groups/new: Form to create a new item"""
137 """GET /repos_groups/new: Form to create a new item"""
138 # url('new_repos_group')
138 # url('new_repos_group')
139 self.__load_defaults()
139 self.__load_defaults()
140 return render('admin/repos_groups/repos_groups_add.html')
140 return render('admin/repos_groups/repos_groups_add.html')
141
141
142 @HasPermissionAnyDecorator('hg.admin')
142 @HasPermissionAnyDecorator('hg.admin')
143 def update(self, id):
143 def update(self, id):
144 """PUT /repos_groups/id: Update an existing item"""
144 """PUT /repos_groups/id: Update an existing item"""
145 # Forms posted to this method should contain a hidden field:
145 # Forms posted to this method should contain a hidden field:
146 # <input type="hidden" name="_method" value="PUT" />
146 # <input type="hidden" name="_method" value="PUT" />
147 # Or using helpers:
147 # Or using helpers:
148 # h.form(url('repos_group', id=ID),
148 # h.form(url('repos_group', id=ID),
149 # method='put')
149 # method='put')
150 # url('repos_group', id=ID)
150 # url('repos_group', id=ID)
151
151
152 self.__load_defaults()
152 self.__load_defaults()
153 c.repos_group = RepoGroup.get(id)
153 c.repos_group = RepoGroup.get(id)
154
154
155 repos_group_form = ReposGroupForm(
155 repos_group_form = ReposGroupForm(
156 edit=True,
156 edit=True,
157 old_data=c.repos_group.get_dict(),
157 old_data=c.repos_group.get_dict(),
158 available_groups=c.repo_groups_choices
158 available_groups=c.repo_groups_choices
159 )()
159 )()
160 try:
160 try:
161 form_result = repos_group_form.to_python(dict(request.POST))
161 form_result = repos_group_form.to_python(dict(request.POST))
162 ReposGroupModel().update(id, form_result)
162 ReposGroupModel().update(id, form_result)
163 Session().commit()
163 Session().commit()
164 h.flash(_('updated repos group %s') \
164 h.flash(_('updated repos group %s') \
165 % form_result['group_name'], category='success')
165 % form_result['group_name'], category='success')
166 #TODO: in future action_logger(, '', '', '', self.sa)
166 #TODO: in future action_logger(, '', '', '', self.sa)
167 except formencode.Invalid, errors:
167 except formencode.Invalid, errors:
168
168
169 return htmlfill.render(
169 return htmlfill.render(
170 render('admin/repos_groups/repos_groups_edit.html'),
170 render('admin/repos_groups/repos_groups_edit.html'),
171 defaults=errors.value,
171 defaults=errors.value,
172 errors=errors.error_dict or {},
172 errors=errors.error_dict or {},
173 prefix_error=False,
173 prefix_error=False,
174 encoding="UTF-8")
174 encoding="UTF-8")
175 except Exception:
175 except Exception:
176 log.error(traceback.format_exc())
176 log.error(traceback.format_exc())
177 h.flash(_('error occurred during update of repos group %s') \
177 h.flash(_('error occurred during update of repos group %s') \
178 % request.POST.get('group_name'), category='error')
178 % request.POST.get('group_name'), category='error')
179
179
180 return redirect(url('edit_repos_group', id=id))
180 return redirect(url('edit_repos_group', id=id))
181
181
182 @HasPermissionAnyDecorator('hg.admin')
182 @HasPermissionAnyDecorator('hg.admin')
183 def delete(self, id):
183 def delete(self, id):
184 """DELETE /repos_groups/id: Delete an existing item"""
184 """DELETE /repos_groups/id: Delete an existing item"""
185 # Forms posted to this method should contain a hidden field:
185 # Forms posted to this method should contain a hidden field:
186 # <input type="hidden" name="_method" value="DELETE" />
186 # <input type="hidden" name="_method" value="DELETE" />
187 # Or using helpers:
187 # Or using helpers:
188 # h.form(url('repos_group', id=ID),
188 # h.form(url('repos_group', id=ID),
189 # method='delete')
189 # method='delete')
190 # url('repos_group', id=ID)
190 # url('repos_group', id=ID)
191
191
192 gr = RepoGroup.get(id)
192 gr = RepoGroup.get(id)
193 repos = gr.repositories.all()
193 repos = gr.repositories.all()
194 if repos:
194 if repos:
195 h.flash(_('This group contains %s repositores and cannot be '
195 h.flash(_('This group contains %s repositores and cannot be '
196 'deleted') % len(repos),
196 'deleted') % len(repos),
197 category='error')
197 category='error')
198 return redirect(url('repos_groups'))
198 return redirect(url('repos_groups'))
199
199
200 try:
200 try:
201 ReposGroupModel().delete(id)
201 ReposGroupModel().delete(id)
202 Session().commit()
202 Session().commit()
203 h.flash(_('removed repos group %s') % gr.group_name,
203 h.flash(_('removed repos group %s') % gr.group_name,
204 category='success')
204 category='success')
205 #TODO: in future action_logger(, '', '', '', self.sa)
205 #TODO: in future action_logger(, '', '', '', self.sa)
206 except IntegrityError, e:
206 except IntegrityError, e:
207 if str(e.message).find('groups_group_parent_id_fkey') != -1:
207 if str(e.message).find('groups_group_parent_id_fkey') != -1:
208 log.error(traceback.format_exc())
208 log.error(traceback.format_exc())
209 h.flash(_('Cannot delete this group it still contains '
209 h.flash(_('Cannot delete this group it still contains '
210 'subgroups'),
210 'subgroups'),
211 category='warning')
211 category='warning')
212 else:
212 else:
213 log.error(traceback.format_exc())
213 log.error(traceback.format_exc())
214 h.flash(_('error occurred during deletion of repos '
214 h.flash(_('error occurred during deletion of repos '
215 'group %s') % gr.group_name, category='error')
215 'group %s') % gr.group_name, category='error')
216
216
217 except Exception:
217 except Exception:
218 log.error(traceback.format_exc())
218 log.error(traceback.format_exc())
219 h.flash(_('error occurred during deletion of repos '
219 h.flash(_('error occurred during deletion of repos '
220 'group %s') % gr.group_name, category='error')
220 'group %s') % gr.group_name, category='error')
221
221
222 return redirect(url('repos_groups'))
222 return redirect(url('repos_groups'))
223
223
224 @HasReposGroupPermissionAnyDecorator('group.admin')
224 @HasReposGroupPermissionAnyDecorator('group.admin')
225 def delete_repos_group_user_perm(self, group_name):
225 def delete_repos_group_user_perm(self, group_name):
226 """
226 """
227 DELETE an existing repositories group permission user
227 DELETE an existing repositories group permission user
228
228
229 :param group_name:
229 :param group_name:
230 """
230 """
231 try:
231 try:
232 recursive = str2bool(request.POST.get('recursive', False))
232 recursive = str2bool(request.POST.get('recursive', False))
233 ReposGroupModel().delete_permission(
233 ReposGroupModel().delete_permission(
234 repos_group=group_name, obj=request.POST['user_id'],
234 repos_group=group_name, obj=request.POST['user_id'],
235 obj_type='user', recursive=recursive
235 obj_type='user', recursive=recursive
236 )
236 )
237 Session().commit()
237 Session().commit()
238 except Exception:
238 except Exception:
239 log.error(traceback.format_exc())
239 log.error(traceback.format_exc())
240 h.flash(_('An error occurred during deletion of group user'),
240 h.flash(_('An error occurred during deletion of group user'),
241 category='error')
241 category='error')
242 raise HTTPInternalServerError()
242 raise HTTPInternalServerError()
243
243
244 @HasReposGroupPermissionAnyDecorator('group.admin')
244 @HasReposGroupPermissionAnyDecorator('group.admin')
245 def delete_repos_group_users_group_perm(self, group_name):
245 def delete_repos_group_users_group_perm(self, group_name):
246 """
246 """
247 DELETE an existing repositories group permission users group
247 DELETE an existing repositories group permission users group
248
248
249 :param group_name:
249 :param group_name:
250 """
250 """
251
251
252 try:
252 try:
253 recursive = str2bool(request.POST.get('recursive', False))
253 recursive = str2bool(request.POST.get('recursive', False))
254 ReposGroupModel().delete_permission(
254 ReposGroupModel().delete_permission(
255 repos_group=group_name, obj=request.POST['users_group_id'],
255 repos_group=group_name, obj=request.POST['users_group_id'],
256 obj_type='users_group', recursive=recursive
256 obj_type='users_group', recursive=recursive
257 )
257 )
258 Session().commit()
258 Session().commit()
259 except Exception:
259 except Exception:
260 log.error(traceback.format_exc())
260 log.error(traceback.format_exc())
261 h.flash(_('An error occurred during deletion of group'
261 h.flash(_('An error occurred during deletion of group'
262 ' users groups'),
262 ' users groups'),
263 category='error')
263 category='error')
264 raise HTTPInternalServerError()
264 raise HTTPInternalServerError()
265
265
266 def show_by_name(self, group_name):
266 def show_by_name(self, group_name):
267 """
267 """
268 This is a proxy that does a lookup group_name -> id, and shows
268 This is a proxy that does a lookup group_name -> id, and shows
269 the group by id view instead
269 the group by id view instead
270 """
270 """
271 group_name = group_name.rstrip('/')
271 group_name = group_name.rstrip('/')
272 id_ = RepoGroup.get_by_group_name(group_name)
272 id_ = RepoGroup.get_by_group_name(group_name)
273 if id_:
273 if id_:
274 return self.show(id_.group_id)
274 return self.show(id_.group_id)
275 raise HTTPNotFound
275 raise HTTPNotFound
276
276
277 @HasReposGroupPermissionAnyDecorator('group.read', 'group.write',
277 @HasReposGroupPermissionAnyDecorator('group.read', 'group.write',
278 'group.admin')
278 'group.admin')
279 def show(self, id, format='html'):
279 def show(self, id, format='html'):
280 """GET /repos_groups/id: Show a specific item"""
280 """GET /repos_groups/id: Show a specific item"""
281 # url('repos_group', id=ID)
281 # url('repos_group', id=ID)
282
282
283 c.group = RepoGroup.get_or_404(id)
283 c.group = RepoGroup.get_or_404(id)
284
284
285 c.group_repos = c.group.repositories.all()
285 c.group_repos = c.group.repositories.all()
286
286
287 #overwrite our cached list with current filter
287 #overwrite our cached list with current filter
288 gr_filter = c.group_repos
288 gr_filter = c.group_repos
289 c.cached_repo_list = self.scm_model.get_repos(all_repos=gr_filter)
289 c.cached_repo_list = self.scm_model.get_repos(all_repos=gr_filter)
290
290
291 c.repos_list = c.cached_repo_list
291 c.repos_list = c.cached_repo_list
292
292
293 c.repo_cnt = 0
293 c.repo_cnt = 0
294
294
295 c.groups = RepoGroup.query().order_by(RepoGroup.group_name)\
295 groups = RepoGroup.query().order_by(RepoGroup.group_name)\
296 .filter(RepoGroup.group_parent_id == id).all()
296 .filter(RepoGroup.group_parent_id == id).all()
297
297 c.groups = self.scm_model.get_repos_groups(groups)
298 return render('admin/repos_groups/repos_groups.html')
298 return render('admin/repos_groups/repos_groups.html')
299
299
300 @HasPermissionAnyDecorator('hg.admin')
300 @HasPermissionAnyDecorator('hg.admin')
301 def edit(self, id, format='html'):
301 def edit(self, id, format='html'):
302 """GET /repos_groups/id/edit: Form to edit an existing item"""
302 """GET /repos_groups/id/edit: Form to edit an existing item"""
303 # url('edit_repos_group', id=ID)
303 # url('edit_repos_group', id=ID)
304
304
305 c.repos_group = ReposGroupModel()._get_repos_group(id)
305 c.repos_group = ReposGroupModel()._get_repos_group(id)
306 defaults = self.__load_data(c.repos_group.group_id)
306 defaults = self.__load_data(c.repos_group.group_id)
307
307
308 # we need to exclude this group from the group list for editing
308 # we need to exclude this group from the group list for editing
309 c.repo_groups = filter(lambda x: x[0] != c.repos_group.group_id,
309 c.repo_groups = filter(lambda x: x[0] != c.repos_group.group_id,
310 c.repo_groups)
310 c.repo_groups)
311
311
312 return htmlfill.render(
312 return htmlfill.render(
313 render('admin/repos_groups/repos_groups_edit.html'),
313 render('admin/repos_groups/repos_groups_edit.html'),
314 defaults=defaults,
314 defaults=defaults,
315 encoding="UTF-8",
315 encoding="UTF-8",
316 force_defaults=False
316 force_defaults=False
317 )
317 )
@@ -1,485 +1,502 b''
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) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 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 import pkg_resources
29 import pkg_resources
30 import platform
30 import platform
31
31
32 from sqlalchemy import func
32 from sqlalchemy import func
33 from formencode import htmlfill
33 from formencode import htmlfill
34 from pylons import request, session, tmpl_context as c, url, config
34 from pylons import request, session, tmpl_context as c, url, config
35 from pylons.controllers.util import abort, redirect
35 from pylons.controllers.util import abort, redirect
36 from pylons.i18n.translation import _
36 from pylons.i18n.translation import _
37
37
38 from rhodecode.lib import helpers as h
38 from rhodecode.lib import helpers as h
39 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
39 from rhodecode.lib.auth import LoginRequired, HasPermissionAllDecorator, \
40 HasPermissionAnyDecorator, NotAnonymous
40 HasPermissionAnyDecorator, NotAnonymous
41 from rhodecode.lib.base import BaseController, render
41 from rhodecode.lib.base import BaseController, render
42 from rhodecode.lib.celerylib import tasks, run_task
42 from rhodecode.lib.celerylib import tasks, run_task
43 from rhodecode.lib.utils import repo2db_mapper, invalidate_cache, \
43 from rhodecode.lib.utils import repo2db_mapper, invalidate_cache, \
44 set_rhodecode_config, repo_name_slug
44 set_rhodecode_config, repo_name_slug
45 from rhodecode.model.db import RhodeCodeUi, Repository, RepoGroup, \
45 from rhodecode.model.db import RhodeCodeUi, Repository, RepoGroup, \
46 RhodeCodeSetting, PullRequest, PullRequestReviewers
46 RhodeCodeSetting, PullRequest, PullRequestReviewers
47 from rhodecode.model.forms import UserForm, ApplicationSettingsForm, \
47 from rhodecode.model.forms import UserForm, ApplicationSettingsForm, \
48 ApplicationUiSettingsForm, ApplicationVisualisationForm
48 ApplicationUiSettingsForm, ApplicationVisualisationForm
49 from rhodecode.model.scm import ScmModel
49 from rhodecode.model.scm import ScmModel
50 from rhodecode.model.user import UserModel
50 from rhodecode.model.user import UserModel
51 from rhodecode.model.db import User
51 from rhodecode.model.db import User
52 from rhodecode.model.notification import EmailNotificationModel
52 from rhodecode.model.notification import EmailNotificationModel
53 from rhodecode.model.meta import Session
53 from rhodecode.model.meta import Session
54 from rhodecode.lib.utils2 import str2bool
54 from rhodecode.lib.utils2 import str2bool
55
55
56 log = logging.getLogger(__name__)
56 log = logging.getLogger(__name__)
57
57
58
58
59 class SettingsController(BaseController):
59 class SettingsController(BaseController):
60 """REST Controller styled on the Atom Publishing Protocol"""
60 """REST Controller styled on the Atom Publishing Protocol"""
61 # To properly map this controller, ensure your config/routing.py
61 # To properly map this controller, ensure your config/routing.py
62 # file has a resource setup:
62 # file has a resource setup:
63 # map.resource('setting', 'settings', controller='admin/settings',
63 # map.resource('setting', 'settings', controller='admin/settings',
64 # path_prefix='/admin', name_prefix='admin_')
64 # path_prefix='/admin', name_prefix='admin_')
65
65
66 @LoginRequired()
66 @LoginRequired()
67 def __before__(self):
67 def __before__(self):
68 c.admin_user = session.get('admin_user')
68 c.admin_user = session.get('admin_user')
69 c.admin_username = session.get('admin_username')
69 c.admin_username = session.get('admin_username')
70 c.modules = sorted([(p.project_name, p.version)
70 c.modules = sorted([(p.project_name, p.version)
71 for p in pkg_resources.working_set],
71 for p in pkg_resources.working_set],
72 key=lambda k: k[0].lower())
72 key=lambda k: k[0].lower())
73 c.py_version = platform.python_version()
73 c.py_version = platform.python_version()
74 c.platform = platform.platform()
74 c.platform = platform.platform()
75 super(SettingsController, self).__before__()
75 super(SettingsController, self).__before__()
76
76
77 @HasPermissionAllDecorator('hg.admin')
77 @HasPermissionAllDecorator('hg.admin')
78 def index(self, format='html'):
78 def index(self, format='html'):
79 """GET /admin/settings: All items in the collection"""
79 """GET /admin/settings: All items in the collection"""
80 # url('admin_settings')
80 # url('admin_settings')
81
81
82 defaults = RhodeCodeSetting.get_app_settings()
82 defaults = RhodeCodeSetting.get_app_settings()
83 defaults.update(self._get_hg_ui_settings())
83 defaults.update(self._get_hg_ui_settings())
84
84
85 return htmlfill.render(
85 return htmlfill.render(
86 render('admin/settings/settings.html'),
86 render('admin/settings/settings.html'),
87 defaults=defaults,
87 defaults=defaults,
88 encoding="UTF-8",
88 encoding="UTF-8",
89 force_defaults=False
89 force_defaults=False
90 )
90 )
91
91
92 @HasPermissionAllDecorator('hg.admin')
92 @HasPermissionAllDecorator('hg.admin')
93 def create(self):
93 def create(self):
94 """POST /admin/settings: Create a new item"""
94 """POST /admin/settings: Create a new item"""
95 # url('admin_settings')
95 # url('admin_settings')
96
96
97 @HasPermissionAllDecorator('hg.admin')
97 @HasPermissionAllDecorator('hg.admin')
98 def new(self, format='html'):
98 def new(self, format='html'):
99 """GET /admin/settings/new: Form to create a new item"""
99 """GET /admin/settings/new: Form to create a new item"""
100 # url('admin_new_setting')
100 # url('admin_new_setting')
101
101
102 @HasPermissionAllDecorator('hg.admin')
102 @HasPermissionAllDecorator('hg.admin')
103 def update(self, setting_id):
103 def update(self, setting_id):
104 """PUT /admin/settings/setting_id: Update an existing item"""
104 """PUT /admin/settings/setting_id: Update an existing item"""
105 # Forms posted to this method should contain a hidden field:
105 # Forms posted to this method should contain a hidden field:
106 # <input type="hidden" name="_method" value="PUT" />
106 # <input type="hidden" name="_method" value="PUT" />
107 # Or using helpers:
107 # Or using helpers:
108 # h.form(url('admin_setting', setting_id=ID),
108 # h.form(url('admin_setting', setting_id=ID),
109 # method='put')
109 # method='put')
110 # url('admin_setting', setting_id=ID)
110 # url('admin_setting', setting_id=ID)
111
111
112 if setting_id == 'mapping':
112 if setting_id == 'mapping':
113 rm_obsolete = request.POST.get('destroy', False)
113 rm_obsolete = request.POST.get('destroy', False)
114 log.debug('Rescanning directories with destroy=%s' % rm_obsolete)
114 log.debug('Rescanning directories with destroy=%s' % rm_obsolete)
115 initial = ScmModel().repo_scan()
115 initial = ScmModel().repo_scan()
116 log.debug('invalidating all repositories')
116 log.debug('invalidating all repositories')
117 for repo_name in initial.keys():
117 for repo_name in initial.keys():
118 invalidate_cache('get_repo_cached_%s' % repo_name)
118 invalidate_cache('get_repo_cached_%s' % repo_name)
119
119
120 added, removed = repo2db_mapper(initial, rm_obsolete)
120 added, removed = repo2db_mapper(initial, rm_obsolete)
121
121
122 h.flash(_('Repositories successfully'
122 h.flash(_('Repositories successfully'
123 ' rescanned added: %s,removed: %s') % (added, removed),
123 ' rescanned added: %s,removed: %s') % (added, removed),
124 category='success')
124 category='success')
125
125
126 if setting_id == 'whoosh':
126 if setting_id == 'whoosh':
127 repo_location = self._get_hg_ui_settings()['paths_root_path']
127 repo_location = self._get_hg_ui_settings()['paths_root_path']
128 full_index = request.POST.get('full_index', False)
128 full_index = request.POST.get('full_index', False)
129 run_task(tasks.whoosh_index, repo_location, full_index)
129 run_task(tasks.whoosh_index, repo_location, full_index)
130 h.flash(_('Whoosh reindex task scheduled'), category='success')
130 h.flash(_('Whoosh reindex task scheduled'), category='success')
131
131
132 if setting_id == 'global':
132 if setting_id == 'global':
133
133
134 application_form = ApplicationSettingsForm()()
134 application_form = ApplicationSettingsForm()()
135 try:
135 try:
136 form_result = application_form.to_python(dict(request.POST))
136 form_result = application_form.to_python(dict(request.POST))
137 except formencode.Invalid, errors:
137 except formencode.Invalid, errors:
138 return htmlfill.render(
138 return htmlfill.render(
139 render('admin/settings/settings.html'),
139 render('admin/settings/settings.html'),
140 defaults=errors.value,
140 defaults=errors.value,
141 errors=errors.error_dict or {},
141 errors=errors.error_dict or {},
142 prefix_error=False,
142 prefix_error=False,
143 encoding="UTF-8"
143 encoding="UTF-8"
144 )
144 )
145
145
146 try:
146 try:
147 sett1 = RhodeCodeSetting.get_by_name_or_create('title')
147 sett1 = RhodeCodeSetting.get_by_name_or_create('title')
148 sett1.app_settings_value = form_result['rhodecode_title']
148 sett1.app_settings_value = form_result['rhodecode_title']
149 Session().add(sett1)
149 Session().add(sett1)
150
150
151 sett2 = RhodeCodeSetting.get_by_name_or_create('realm')
151 sett2 = RhodeCodeSetting.get_by_name_or_create('realm')
152 sett2.app_settings_value = form_result['rhodecode_realm']
152 sett2.app_settings_value = form_result['rhodecode_realm']
153 Session().add(sett2)
153 Session().add(sett2)
154
154
155 sett3 = RhodeCodeSetting.get_by_name_or_create('ga_code')
155 sett3 = RhodeCodeSetting.get_by_name_or_create('ga_code')
156 sett3.app_settings_value = form_result['rhodecode_ga_code']
156 sett3.app_settings_value = form_result['rhodecode_ga_code']
157 Session().add(sett3)
157 Session().add(sett3)
158
158
159 Session().commit()
159 Session().commit()
160 set_rhodecode_config(config)
160 set_rhodecode_config(config)
161 h.flash(_('Updated application settings'), category='success')
161 h.flash(_('Updated application settings'), category='success')
162
162
163 except Exception:
163 except Exception:
164 log.error(traceback.format_exc())
164 log.error(traceback.format_exc())
165 h.flash(_('error occurred during updating '
165 h.flash(_('error occurred during updating '
166 'application settings'),
166 'application settings'),
167 category='error')
167 category='error')
168
168
169 if setting_id == 'visual':
169 if setting_id == 'visual':
170
170
171 application_form = ApplicationVisualisationForm()()
171 application_form = ApplicationVisualisationForm()()
172 try:
172 try:
173 form_result = application_form.to_python(dict(request.POST))
173 form_result = application_form.to_python(dict(request.POST))
174 except formencode.Invalid, errors:
174 except formencode.Invalid, errors:
175 return htmlfill.render(
175 return htmlfill.render(
176 render('admin/settings/settings.html'),
176 render('admin/settings/settings.html'),
177 defaults=errors.value,
177 defaults=errors.value,
178 errors=errors.error_dict or {},
178 errors=errors.error_dict or {},
179 prefix_error=False,
179 prefix_error=False,
180 encoding="UTF-8"
180 encoding="UTF-8"
181 )
181 )
182
182
183 try:
183 try:
184 sett1 = RhodeCodeSetting.get_by_name_or_create('show_public_icon')
184 sett1 = RhodeCodeSetting.get_by_name_or_create('show_public_icon')
185 sett1.app_settings_value = \
185 sett1.app_settings_value = \
186 form_result['rhodecode_show_public_icon']
186 form_result['rhodecode_show_public_icon']
187
187
188 sett2 = RhodeCodeSetting.get_by_name_or_create('show_private_icon')
188 sett2 = RhodeCodeSetting.get_by_name_or_create('show_private_icon')
189 sett2.app_settings_value = \
189 sett2.app_settings_value = \
190 form_result['rhodecode_show_private_icon']
190 form_result['rhodecode_show_private_icon']
191
191
192 sett3 = RhodeCodeSetting.get_by_name_or_create('stylify_metatags')
192 sett3 = RhodeCodeSetting.get_by_name_or_create('stylify_metatags')
193 sett3.app_settings_value = \
193 sett3.app_settings_value = \
194 form_result['rhodecode_stylify_metatags']
194 form_result['rhodecode_stylify_metatags']
195
195
196 Session().add(sett1)
196 Session().add(sett1)
197 Session().add(sett2)
197 Session().add(sett2)
198 Session().add(sett3)
198 Session().add(sett3)
199 Session().commit()
199 Session().commit()
200 set_rhodecode_config(config)
200 set_rhodecode_config(config)
201 h.flash(_('Updated visualisation settings'),
201 h.flash(_('Updated visualisation settings'),
202 category='success')
202 category='success')
203
203
204 except Exception:
204 except Exception:
205 log.error(traceback.format_exc())
205 log.error(traceback.format_exc())
206 h.flash(_('error occurred during updating '
206 h.flash(_('error occurred during updating '
207 'visualisation settings'),
207 'visualisation settings'),
208 category='error')
208 category='error')
209
209
210 if setting_id == 'vcs':
210 if setting_id == 'vcs':
211 application_form = ApplicationUiSettingsForm()()
211 application_form = ApplicationUiSettingsForm()()
212 try:
212 try:
213 form_result = application_form.to_python(dict(request.POST))
213 form_result = application_form.to_python(dict(request.POST))
214 except formencode.Invalid, errors:
214 except formencode.Invalid, errors:
215 return htmlfill.render(
215 return htmlfill.render(
216 render('admin/settings/settings.html'),
216 render('admin/settings/settings.html'),
217 defaults=errors.value,
217 defaults=errors.value,
218 errors=errors.error_dict or {},
218 errors=errors.error_dict or {},
219 prefix_error=False,
219 prefix_error=False,
220 encoding="UTF-8"
220 encoding="UTF-8"
221 )
221 )
222
222
223 try:
223 try:
224 # fix namespaces for hooks and extensions
224 # fix namespaces for hooks and extensions
225 _f = lambda s: s.replace('.', '_')
225 _f = lambda s: s.replace('.', '_')
226
226
227 sett = RhodeCodeUi.get_by_key('push_ssl')
227 sett = RhodeCodeUi.get_by_key('push_ssl')
228 sett.ui_value = form_result['web_push_ssl']
228 sett.ui_value = form_result['web_push_ssl']
229 Session().add(sett)
229 Session().add(sett)
230
230
231 sett = RhodeCodeUi.get_by_key('/')
231 sett = RhodeCodeUi.get_by_key('/')
232 sett.ui_value = form_result['paths_root_path']
232 sett.ui_value = form_result['paths_root_path']
233 Session().add(sett)
233 Session().add(sett)
234
234
235 #HOOKS
235 #HOOKS
236 sett = RhodeCodeUi.get_by_key(RhodeCodeUi.HOOK_UPDATE)
236 sett = RhodeCodeUi.get_by_key(RhodeCodeUi.HOOK_UPDATE)
237 sett.ui_active = form_result[_f('hooks_%s' %
237 sett.ui_active = form_result[_f('hooks_%s' %
238 RhodeCodeUi.HOOK_UPDATE)]
238 RhodeCodeUi.HOOK_UPDATE)]
239 Session().add(sett)
239 Session().add(sett)
240
240
241 sett = RhodeCodeUi.get_by_key(RhodeCodeUi.HOOK_REPO_SIZE)
241 sett = RhodeCodeUi.get_by_key(RhodeCodeUi.HOOK_REPO_SIZE)
242 sett.ui_active = form_result[_f('hooks_%s' %
242 sett.ui_active = form_result[_f('hooks_%s' %
243 RhodeCodeUi.HOOK_REPO_SIZE)]
243 RhodeCodeUi.HOOK_REPO_SIZE)]
244 Session().add(sett)
244 Session().add(sett)
245
245
246 sett = RhodeCodeUi.get_by_key(RhodeCodeUi.HOOK_PUSH)
246 sett = RhodeCodeUi.get_by_key(RhodeCodeUi.HOOK_PUSH)
247 sett.ui_active = form_result[_f('hooks_%s' %
247 sett.ui_active = form_result[_f('hooks_%s' %
248 RhodeCodeUi.HOOK_PUSH)]
248 RhodeCodeUi.HOOK_PUSH)]
249 Session().add(sett)
249 Session().add(sett)
250
250
251 sett = RhodeCodeUi.get_by_key(RhodeCodeUi.HOOK_PULL)
251 sett = RhodeCodeUi.get_by_key(RhodeCodeUi.HOOK_PULL)
252 sett.ui_active = form_result[_f('hooks_%s' %
252 sett.ui_active = form_result[_f('hooks_%s' %
253 RhodeCodeUi.HOOK_PULL)]
253 RhodeCodeUi.HOOK_PULL)]
254
254
255 Session().add(sett)
255 Session().add(sett)
256
256
257 ## EXTENSIONS
257 ## EXTENSIONS
258 sett = RhodeCodeUi.get_by_key('largefiles')
258 sett = RhodeCodeUi.get_by_key('largefiles')
259 if not sett:
260 #make one if it's not there !
261 sett = RhodeCodeUi()
262 sett.ui_key = 'largefiles'
263 sett.ui_section = 'extensions'
259 sett.ui_active = form_result[_f('extensions_largefiles')]
264 sett.ui_active = form_result[_f('extensions_largefiles')]
260 Session().add(sett)
265 Session().add(sett)
261
266
262 sett = RhodeCodeUi.get_by_key('hgsubversion')
267 sett = RhodeCodeUi.get_by_key('hgsubversion')
268 if not sett:
269 #make one if it's not there !
270 sett = RhodeCodeUi()
271 sett.ui_key = 'hgsubversion'
272 sett.ui_section = 'extensions'
273
263 sett.ui_active = form_result[_f('extensions_hgsubversion')]
274 sett.ui_active = form_result[_f('extensions_hgsubversion')]
264 Session().add(sett)
275 Session().add(sett)
265
276
266 # sett = RhodeCodeUi.get_by_key('hggit')
277 # sett = RhodeCodeUi.get_by_key('hggit')
278 # if not sett:
279 # #make one if it's not there !
280 # sett = RhodeCodeUi()
281 # sett.ui_key = 'hggit'
282 # sett.ui_section = 'extensions'
283 #
267 # sett.ui_active = form_result[_f('extensions_hggit')]
284 # sett.ui_active = form_result[_f('extensions_hggit')]
268 # Session().add(sett)
285 # Session().add(sett)
269
286
270 Session().commit()
287 Session().commit()
271
288
272 h.flash(_('Updated VCS settings'), category='success')
289 h.flash(_('Updated VCS settings'), category='success')
273
290
274 except Exception:
291 except Exception:
275 log.error(traceback.format_exc())
292 log.error(traceback.format_exc())
276 h.flash(_('error occurred during updating '
293 h.flash(_('error occurred during updating '
277 'application settings'), category='error')
294 'application settings'), category='error')
278
295
279 if setting_id == 'hooks':
296 if setting_id == 'hooks':
280 ui_key = request.POST.get('new_hook_ui_key')
297 ui_key = request.POST.get('new_hook_ui_key')
281 ui_value = request.POST.get('new_hook_ui_value')
298 ui_value = request.POST.get('new_hook_ui_value')
282 try:
299 try:
283
300
284 if ui_value and ui_key:
301 if ui_value and ui_key:
285 RhodeCodeUi.create_or_update_hook(ui_key, ui_value)
302 RhodeCodeUi.create_or_update_hook(ui_key, ui_value)
286 h.flash(_('Added new hook'),
303 h.flash(_('Added new hook'),
287 category='success')
304 category='success')
288
305
289 # check for edits
306 # check for edits
290 update = False
307 update = False
291 _d = request.POST.dict_of_lists()
308 _d = request.POST.dict_of_lists()
292 for k, v in zip(_d.get('hook_ui_key', []),
309 for k, v in zip(_d.get('hook_ui_key', []),
293 _d.get('hook_ui_value_new', [])):
310 _d.get('hook_ui_value_new', [])):
294 RhodeCodeUi.create_or_update_hook(k, v)
311 RhodeCodeUi.create_or_update_hook(k, v)
295 update = True
312 update = True
296
313
297 if update:
314 if update:
298 h.flash(_('Updated hooks'), category='success')
315 h.flash(_('Updated hooks'), category='success')
299 Session().commit()
316 Session().commit()
300 except Exception:
317 except Exception:
301 log.error(traceback.format_exc())
318 log.error(traceback.format_exc())
302 h.flash(_('error occurred during hook creation'),
319 h.flash(_('error occurred during hook creation'),
303 category='error')
320 category='error')
304
321
305 return redirect(url('admin_edit_setting', setting_id='hooks'))
322 return redirect(url('admin_edit_setting', setting_id='hooks'))
306
323
307 if setting_id == 'email':
324 if setting_id == 'email':
308 test_email = request.POST.get('test_email')
325 test_email = request.POST.get('test_email')
309 test_email_subj = 'RhodeCode TestEmail'
326 test_email_subj = 'RhodeCode TestEmail'
310 test_email_body = 'RhodeCode Email test'
327 test_email_body = 'RhodeCode Email test'
311
328
312 test_email_html_body = EmailNotificationModel()\
329 test_email_html_body = EmailNotificationModel()\
313 .get_email_tmpl(EmailNotificationModel.TYPE_DEFAULT,
330 .get_email_tmpl(EmailNotificationModel.TYPE_DEFAULT,
314 body=test_email_body)
331 body=test_email_body)
315
332
316 recipients = [test_email] if [test_email] else None
333 recipients = [test_email] if [test_email] else None
317
334
318 run_task(tasks.send_email, recipients, test_email_subj,
335 run_task(tasks.send_email, recipients, test_email_subj,
319 test_email_body, test_email_html_body)
336 test_email_body, test_email_html_body)
320
337
321 h.flash(_('Email task created'), category='success')
338 h.flash(_('Email task created'), category='success')
322 return redirect(url('admin_settings'))
339 return redirect(url('admin_settings'))
323
340
324 @HasPermissionAllDecorator('hg.admin')
341 @HasPermissionAllDecorator('hg.admin')
325 def delete(self, setting_id):
342 def delete(self, setting_id):
326 """DELETE /admin/settings/setting_id: Delete an existing item"""
343 """DELETE /admin/settings/setting_id: Delete an existing item"""
327 # Forms posted to this method should contain a hidden field:
344 # Forms posted to this method should contain a hidden field:
328 # <input type="hidden" name="_method" value="DELETE" />
345 # <input type="hidden" name="_method" value="DELETE" />
329 # Or using helpers:
346 # Or using helpers:
330 # h.form(url('admin_setting', setting_id=ID),
347 # h.form(url('admin_setting', setting_id=ID),
331 # method='delete')
348 # method='delete')
332 # url('admin_setting', setting_id=ID)
349 # url('admin_setting', setting_id=ID)
333 if setting_id == 'hooks':
350 if setting_id == 'hooks':
334 hook_id = request.POST.get('hook_id')
351 hook_id = request.POST.get('hook_id')
335 RhodeCodeUi.delete(hook_id)
352 RhodeCodeUi.delete(hook_id)
336 Session().commit()
353 Session().commit()
337
354
338 @HasPermissionAllDecorator('hg.admin')
355 @HasPermissionAllDecorator('hg.admin')
339 def show(self, setting_id, format='html'):
356 def show(self, setting_id, format='html'):
340 """
357 """
341 GET /admin/settings/setting_id: Show a specific item"""
358 GET /admin/settings/setting_id: Show a specific item"""
342 # url('admin_setting', setting_id=ID)
359 # url('admin_setting', setting_id=ID)
343
360
344 @HasPermissionAllDecorator('hg.admin')
361 @HasPermissionAllDecorator('hg.admin')
345 def edit(self, setting_id, format='html'):
362 def edit(self, setting_id, format='html'):
346 """
363 """
347 GET /admin/settings/setting_id/edit: Form to
364 GET /admin/settings/setting_id/edit: Form to
348 edit an existing item"""
365 edit an existing item"""
349 # url('admin_edit_setting', setting_id=ID)
366 # url('admin_edit_setting', setting_id=ID)
350
367
351 c.hooks = RhodeCodeUi.get_builtin_hooks()
368 c.hooks = RhodeCodeUi.get_builtin_hooks()
352 c.custom_hooks = RhodeCodeUi.get_custom_hooks()
369 c.custom_hooks = RhodeCodeUi.get_custom_hooks()
353
370
354 return htmlfill.render(
371 return htmlfill.render(
355 render('admin/settings/hooks.html'),
372 render('admin/settings/hooks.html'),
356 defaults={},
373 defaults={},
357 encoding="UTF-8",
374 encoding="UTF-8",
358 force_defaults=False
375 force_defaults=False
359 )
376 )
360
377
361 @NotAnonymous()
378 @NotAnonymous()
362 def my_account(self):
379 def my_account(self):
363 """
380 """
364 GET /_admin/my_account Displays info about my account
381 GET /_admin/my_account Displays info about my account
365 """
382 """
366 # url('admin_settings_my_account')
383 # url('admin_settings_my_account')
367
384
368 c.user = User.get(self.rhodecode_user.user_id)
385 c.user = User.get(self.rhodecode_user.user_id)
369 all_repos = Session().query(Repository)\
386 all_repos = Session().query(Repository)\
370 .filter(Repository.user_id == c.user.user_id)\
387 .filter(Repository.user_id == c.user.user_id)\
371 .order_by(func.lower(Repository.repo_name)).all()
388 .order_by(func.lower(Repository.repo_name)).all()
372
389
373 c.user_repos = ScmModel().get_repos(all_repos)
390 c.user_repos = ScmModel().get_repos(all_repos)
374
391
375 if c.user.username == 'default':
392 if c.user.username == 'default':
376 h.flash(_("You can't edit this user since it's"
393 h.flash(_("You can't edit this user since it's"
377 " crucial for entire application"), category='warning')
394 " crucial for entire application"), category='warning')
378 return redirect(url('users'))
395 return redirect(url('users'))
379
396
380 defaults = c.user.get_dict()
397 defaults = c.user.get_dict()
381
398
382 c.form = htmlfill.render(
399 c.form = htmlfill.render(
383 render('admin/users/user_edit_my_account_form.html'),
400 render('admin/users/user_edit_my_account_form.html'),
384 defaults=defaults,
401 defaults=defaults,
385 encoding="UTF-8",
402 encoding="UTF-8",
386 force_defaults=False
403 force_defaults=False
387 )
404 )
388 return render('admin/users/user_edit_my_account.html')
405 return render('admin/users/user_edit_my_account.html')
389
406
390 @NotAnonymous()
407 @NotAnonymous()
391 def my_account_update(self):
408 def my_account_update(self):
392 """PUT /_admin/my_account_update: Update an existing item"""
409 """PUT /_admin/my_account_update: Update an existing item"""
393 # Forms posted to this method should contain a hidden field:
410 # Forms posted to this method should contain a hidden field:
394 # <input type="hidden" name="_method" value="PUT" />
411 # <input type="hidden" name="_method" value="PUT" />
395 # Or using helpers:
412 # Or using helpers:
396 # h.form(url('admin_settings_my_account_update'),
413 # h.form(url('admin_settings_my_account_update'),
397 # method='put')
414 # method='put')
398 # url('admin_settings_my_account_update', id=ID)
415 # url('admin_settings_my_account_update', id=ID)
399 uid = self.rhodecode_user.user_id
416 uid = self.rhodecode_user.user_id
400 email = self.rhodecode_user.email
417 email = self.rhodecode_user.email
401 _form = UserForm(edit=True,
418 _form = UserForm(edit=True,
402 old_data={'user_id': uid, 'email': email})()
419 old_data={'user_id': uid, 'email': email})()
403 form_result = {}
420 form_result = {}
404 try:
421 try:
405 form_result = _form.to_python(dict(request.POST))
422 form_result = _form.to_python(dict(request.POST))
406 UserModel().update_my_account(uid, form_result)
423 UserModel().update_my_account(uid, form_result)
407 h.flash(_('Your account was updated successfully'),
424 h.flash(_('Your account was updated successfully'),
408 category='success')
425 category='success')
409 Session().commit()
426 Session().commit()
410 except formencode.Invalid, errors:
427 except formencode.Invalid, errors:
411 c.user = User.get(self.rhodecode_user.user_id)
428 c.user = User.get(self.rhodecode_user.user_id)
412
429
413 c.form = htmlfill.render(
430 c.form = htmlfill.render(
414 render('admin/users/user_edit_my_account_form.html'),
431 render('admin/users/user_edit_my_account_form.html'),
415 defaults=errors.value,
432 defaults=errors.value,
416 errors=errors.error_dict or {},
433 errors=errors.error_dict or {},
417 prefix_error=False,
434 prefix_error=False,
418 encoding="UTF-8")
435 encoding="UTF-8")
419 return render('admin/users/user_edit_my_account.html')
436 return render('admin/users/user_edit_my_account.html')
420 except Exception:
437 except Exception:
421 log.error(traceback.format_exc())
438 log.error(traceback.format_exc())
422 h.flash(_('error occurred during update of user %s') \
439 h.flash(_('error occurred during update of user %s') \
423 % form_result.get('username'), category='error')
440 % form_result.get('username'), category='error')
424
441
425 return redirect(url('my_account'))
442 return redirect(url('my_account'))
426
443
427 @NotAnonymous()
444 @NotAnonymous()
428 def my_account_my_repos(self):
445 def my_account_my_repos(self):
429 all_repos = Session().query(Repository)\
446 all_repos = Session().query(Repository)\
430 .filter(Repository.user_id == self.rhodecode_user.user_id)\
447 .filter(Repository.user_id == self.rhodecode_user.user_id)\
431 .order_by(func.lower(Repository.repo_name))\
448 .order_by(func.lower(Repository.repo_name))\
432 .all()
449 .all()
433 c.user_repos = ScmModel().get_repos(all_repos)
450 c.user_repos = ScmModel().get_repos(all_repos)
434 return render('admin/users/user_edit_my_account_repos.html')
451 return render('admin/users/user_edit_my_account_repos.html')
435
452
436 @NotAnonymous()
453 @NotAnonymous()
437 def my_account_my_pullrequests(self):
454 def my_account_my_pullrequests(self):
438 c.my_pull_requests = PullRequest.query()\
455 c.my_pull_requests = PullRequest.query()\
439 .filter(PullRequest.user_id==
456 .filter(PullRequest.user_id==
440 self.rhodecode_user.user_id)\
457 self.rhodecode_user.user_id)\
441 .all()
458 .all()
442 c.participate_in_pull_requests = \
459 c.participate_in_pull_requests = \
443 [x.pull_request for x in PullRequestReviewers.query()\
460 [x.pull_request for x in PullRequestReviewers.query()\
444 .filter(PullRequestReviewers.user_id==
461 .filter(PullRequestReviewers.user_id==
445 self.rhodecode_user.user_id)\
462 self.rhodecode_user.user_id)\
446 .all()]
463 .all()]
447 return render('admin/users/user_edit_my_account_pullrequests.html')
464 return render('admin/users/user_edit_my_account_pullrequests.html')
448
465
449 @NotAnonymous()
466 @NotAnonymous()
450 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
467 @HasPermissionAnyDecorator('hg.admin', 'hg.create.repository')
451 def create_repository(self):
468 def create_repository(self):
452 """GET /_admin/create_repository: Form to create a new item"""
469 """GET /_admin/create_repository: Form to create a new item"""
453
470
454 c.repo_groups = RepoGroup.groups_choices()
471 c.repo_groups = RepoGroup.groups_choices(check_perms=True)
455 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
472 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
456 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
473 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
457
474
458 new_repo = request.GET.get('repo', '')
475 new_repo = request.GET.get('repo', '')
459 c.new_repo = repo_name_slug(new_repo)
476 c.new_repo = repo_name_slug(new_repo)
460
477
461 return render('admin/repos/repo_add_create_repository.html')
478 return render('admin/repos/repo_add_create_repository.html')
462
479
463 def _get_hg_ui_settings(self):
480 def _get_hg_ui_settings(self):
464 ret = RhodeCodeUi.query().all()
481 ret = RhodeCodeUi.query().all()
465
482
466 if not ret:
483 if not ret:
467 raise Exception('Could not get application ui settings !')
484 raise Exception('Could not get application ui settings !')
468 settings = {}
485 settings = {}
469 for each in ret:
486 for each in ret:
470 k = each.ui_key
487 k = each.ui_key
471 v = each.ui_value
488 v = each.ui_value
472 if k == '/':
489 if k == '/':
473 k = 'root_path'
490 k = 'root_path'
474
491
475 if k == 'push_ssl':
492 if k == 'push_ssl':
476 v = str2bool(v)
493 v = str2bool(v)
477
494
478 if k.find('.') != -1:
495 if k.find('.') != -1:
479 k = k.replace('.', '_')
496 k = k.replace('.', '_')
480
497
481 if each.ui_section in ['hooks', 'extensions']:
498 if each.ui_section in ['hooks', 'extensions']:
482 v = each.ui_active
499 v = each.ui_active
483
500
484 settings[each.ui_section + '_' + k] = v
501 settings[each.ui_section + '_' + k] = v
485 return settings
502 return settings
@@ -1,124 +1,125 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.changelog
3 rhodecode.controllers.changelog
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 changelog controller for rhodecode
6 changelog controller for rhodecode
7
7
8 :created_on: Apr 21, 2010
8 :created_on: Apr 21, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 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
28
29 from pylons import request, url, session, tmpl_context as c
29 from pylons import request, url, session, tmpl_context as c
30 from pylons.controllers.util import redirect
30 from pylons.controllers.util import redirect
31 from pylons.i18n.translation import _
31 from pylons.i18n.translation import _
32
32
33 import rhodecode.lib.helpers as h
33 import rhodecode.lib.helpers as h
34 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
34 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
35 from rhodecode.lib.base import BaseRepoController, render
35 from rhodecode.lib.base import BaseRepoController, render
36 from rhodecode.lib.helpers import RepoPage
36 from rhodecode.lib.helpers import RepoPage
37 from rhodecode.lib.compat import json
37 from rhodecode.lib.compat import json
38 from rhodecode.lib.graphmod import _colored, _dagwalker
38 from rhodecode.lib.graphmod import _colored, _dagwalker
39 from rhodecode.lib.vcs.exceptions import RepositoryError, ChangesetDoesNotExistError
39 from rhodecode.lib.vcs.exceptions import RepositoryError, ChangesetDoesNotExistError
40 from rhodecode.lib.utils2 import safe_int
40
41
41 log = logging.getLogger(__name__)
42 log = logging.getLogger(__name__)
42
43
43
44
44 class ChangelogController(BaseRepoController):
45 class ChangelogController(BaseRepoController):
45
46
46 @LoginRequired()
47 @LoginRequired()
47 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
48 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
48 'repository.admin')
49 'repository.admin')
49 def __before__(self):
50 def __before__(self):
50 super(ChangelogController, self).__before__()
51 super(ChangelogController, self).__before__()
51 c.affected_files_cut_off = 60
52 c.affected_files_cut_off = 60
52
53
53 def index(self):
54 def index(self):
54 limit = 100
55 limit = 100
55 default = 20
56 default = 20
56 if request.params.get('size'):
57 if request.params.get('size'):
57 try:
58 try:
58 int_size = int(request.params.get('size'))
59 int_size = int(request.params.get('size'))
59 except ValueError:
60 except ValueError:
60 int_size = default
61 int_size = default
61 c.size = max(min(int_size, limit), 1)
62 c.size = max(min(int_size, limit), 1)
62 session['changelog_size'] = c.size
63 session['changelog_size'] = c.size
63 session.save()
64 session.save()
64 else:
65 else:
65 c.size = int(session.get('changelog_size', default))
66 c.size = int(session.get('changelog_size', default))
66 # min size must be 1
67 # min size must be 1
67 c.size = max(c.size, 1)
68 c.size = max(c.size, 1)
68 p = int(request.params.get('page', 1))
69 p = safe_int(request.params.get('page', 1), 1)
69 branch_name = request.params.get('branch', None)
70 branch_name = request.params.get('branch', None)
70 try:
71 try:
71 if branch_name:
72 if branch_name:
72 collection = [z for z in
73 collection = [z for z in
73 c.rhodecode_repo.get_changesets(start=0,
74 c.rhodecode_repo.get_changesets(start=0,
74 branch_name=branch_name)]
75 branch_name=branch_name)]
75 c.total_cs = len(collection)
76 c.total_cs = len(collection)
76 else:
77 else:
77 collection = c.rhodecode_repo
78 collection = c.rhodecode_repo
78 c.total_cs = len(c.rhodecode_repo)
79 c.total_cs = len(c.rhodecode_repo)
79
80
80 c.pagination = RepoPage(collection, page=p, item_count=c.total_cs,
81 c.pagination = RepoPage(collection, page=p, item_count=c.total_cs,
81 items_per_page=c.size, branch=branch_name)
82 items_per_page=c.size, branch=branch_name)
82 collection = list(c.pagination)
83 collection = list(c.pagination)
83 page_revisions = [x.raw_id for x in collection]
84 page_revisions = [x.raw_id for x in collection]
84 c.comments = c.rhodecode_db_repo.get_comments(page_revisions)
85 c.comments = c.rhodecode_db_repo.get_comments(page_revisions)
85 c.statuses = c.rhodecode_db_repo.statuses(page_revisions)
86 c.statuses = c.rhodecode_db_repo.statuses(page_revisions)
86 except (RepositoryError, ChangesetDoesNotExistError, Exception), e:
87 except (RepositoryError, ChangesetDoesNotExistError, Exception), e:
87 log.error(traceback.format_exc())
88 log.error(traceback.format_exc())
88 h.flash(str(e), category='warning')
89 h.flash(str(e), category='warning')
89 return redirect(url('home'))
90 return redirect(url('home'))
90
91
91 self._graph(c.rhodecode_repo, collection, c.total_cs, c.size, p)
92 self._graph(c.rhodecode_repo, collection, c.total_cs, c.size, p)
92
93
93 c.branch_name = branch_name
94 c.branch_name = branch_name
94 c.branch_filters = [('', _('All Branches'))] + \
95 c.branch_filters = [('', _('All Branches'))] + \
95 [(k, k) for k in c.rhodecode_repo.branches.keys()]
96 [(k, k) for k in c.rhodecode_repo.branches.keys()]
96
97
97 return render('changelog/changelog.html')
98 return render('changelog/changelog.html')
98
99
99 def changelog_details(self, cs):
100 def changelog_details(self, cs):
100 if request.environ.get('HTTP_X_PARTIAL_XHR'):
101 if request.environ.get('HTTP_X_PARTIAL_XHR'):
101 c.cs = c.rhodecode_repo.get_changeset(cs)
102 c.cs = c.rhodecode_repo.get_changeset(cs)
102 return render('changelog/changelog_details.html')
103 return render('changelog/changelog_details.html')
103
104
104 def _graph(self, repo, collection, repo_size, size, p):
105 def _graph(self, repo, collection, repo_size, size, p):
105 """
106 """
106 Generates a DAG graph for mercurial
107 Generates a DAG graph for mercurial
107
108
108 :param repo: repo instance
109 :param repo: repo instance
109 :param size: number of commits to show
110 :param size: number of commits to show
110 :param p: page number
111 :param p: page number
111 """
112 """
112 if not collection:
113 if not collection:
113 c.jsdata = json.dumps([])
114 c.jsdata = json.dumps([])
114 return
115 return
115
116
116 data = []
117 data = []
117 revs = [x.revision for x in collection]
118 revs = [x.revision for x in collection]
118
119
119 dag = _dagwalker(repo, revs, repo.alias)
120 dag = _dagwalker(repo, revs, repo.alias)
120 dag = _colored(dag)
121 dag = _colored(dag)
121 for (id, type, ctx, vtx, edges) in dag:
122 for (id, type, ctx, vtx, edges) in dag:
122 data.append(['', vtx, edges])
123 data.append(['', vtx, edges])
123
124
124 c.jsdata = json.dumps(data)
125 c.jsdata = json.dumps(data)
@@ -1,133 +1,138 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.compare
3 rhodecode.controllers.compare
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 compare controller for pylons showoing differences between two
6 compare controller for pylons showoing differences between two
7 repos, branches, bookmarks or tips
7 repos, branches, bookmarks or tips
8
8
9 :created_on: May 6, 2012
9 :created_on: May 6, 2012
10 :author: marcink
10 :author: marcink
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2010-2012 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 logging
26 import logging
27 import traceback
27 import traceback
28
28
29 from webob.exc import HTTPNotFound
29 from webob.exc import HTTPNotFound
30 from pylons import request, response, session, tmpl_context as c, url
30 from pylons import request, response, session, tmpl_context as c, url
31 from pylons.controllers.util import abort, redirect
31 from pylons.controllers.util import abort, redirect
32 from pylons.i18n.translation import _
32 from pylons.i18n.translation import _
33
33
34 from rhodecode.lib.vcs.exceptions import EmptyRepositoryError, RepositoryError
34 from rhodecode.lib.vcs.exceptions import EmptyRepositoryError, RepositoryError
35 from rhodecode.lib import helpers as h
35 from rhodecode.lib import helpers as h
36 from rhodecode.lib.base import BaseRepoController, render
36 from rhodecode.lib.base import BaseRepoController, render
37 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
37 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
38 from rhodecode.lib import diffs
38 from rhodecode.lib import diffs
39
39
40 from rhodecode.model.db import Repository
40 from rhodecode.model.db import Repository
41 from rhodecode.model.pull_request import PullRequestModel
41 from rhodecode.model.pull_request import PullRequestModel
42 from webob.exc import HTTPBadRequest
42
43
43 log = logging.getLogger(__name__)
44 log = logging.getLogger(__name__)
44
45
45
46
46 class CompareController(BaseRepoController):
47 class CompareController(BaseRepoController):
47
48
48 @LoginRequired()
49 @LoginRequired()
49 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
50 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
50 'repository.admin')
51 'repository.admin')
51 def __before__(self):
52 def __before__(self):
52 super(CompareController, self).__before__()
53 super(CompareController, self).__before__()
53
54
54 def __get_cs_or_redirect(self, rev, repo, redirect_after=True):
55 def __get_cs_or_redirect(self, rev, repo, redirect_after=True,
56 partial=False):
55 """
57 """
56 Safe way to get changeset if error occur it redirects to changeset with
58 Safe way to get changeset if error occur it redirects to changeset with
57 proper message
59 proper message. If partial is set then don't do redirect raise Exception
60 instead
58
61
59 :param rev: revision to fetch
62 :param rev: revision to fetch
60 :param repo: repo instance
63 :param repo: repo instance
61 """
64 """
62
65
63 try:
66 try:
64 type_, rev = rev
67 type_, rev = rev
65 return repo.scm_instance.get_changeset(rev)
68 return repo.scm_instance.get_changeset(rev)
66 except EmptyRepositoryError, e:
69 except EmptyRepositoryError, e:
67 if not redirect_after:
70 if not redirect_after:
68 return None
71 return None
69 h.flash(h.literal(_('There are no changesets yet')),
72 h.flash(h.literal(_('There are no changesets yet')),
70 category='warning')
73 category='warning')
71 redirect(url('summary_home', repo_name=repo.repo_name))
74 redirect(url('summary_home', repo_name=repo.repo_name))
72
75
73 except RepositoryError, e:
76 except RepositoryError, e:
74 log.error(traceback.format_exc())
77 log.error(traceback.format_exc())
75 h.flash(str(e), category='warning')
78 h.flash(str(e), category='warning')
76 redirect(h.url('summary_home', repo_name=repo.repo_name))
79 if not partial:
80 redirect(h.url('summary_home', repo_name=repo.repo_name))
81 raise HTTPBadRequest()
77
82
78 def index(self, org_ref_type, org_ref, other_ref_type, other_ref):
83 def index(self, org_ref_type, org_ref, other_ref_type, other_ref):
79
84
80 org_repo = c.rhodecode_db_repo.repo_name
85 org_repo = c.rhodecode_db_repo.repo_name
81 org_ref = (org_ref_type, org_ref)
86 org_ref = (org_ref_type, org_ref)
82 other_ref = (other_ref_type, other_ref)
87 other_ref = (other_ref_type, other_ref)
83 other_repo = request.GET.get('repo', org_repo)
88 other_repo = request.GET.get('repo', org_repo)
84
89
85 c.swap_url = h.url('compare_url', repo_name=other_repo,
90 c.swap_url = h.url('compare_url', repo_name=other_repo,
86 org_ref_type=other_ref[0], org_ref=other_ref[1],
91 org_ref_type=other_ref[0], org_ref=other_ref[1],
87 other_ref_type=org_ref[0], other_ref=org_ref[1],
92 other_ref_type=org_ref[0], other_ref=org_ref[1],
88 repo=org_repo)
93 repo=org_repo)
89
94
90 c.org_repo = org_repo = Repository.get_by_repo_name(org_repo)
95 c.org_repo = org_repo = Repository.get_by_repo_name(org_repo)
91 c.other_repo = other_repo = Repository.get_by_repo_name(other_repo)
96 c.other_repo = other_repo = Repository.get_by_repo_name(other_repo)
92
97
93 if c.org_repo is None or c.other_repo is None:
98 if c.org_repo is None or c.other_repo is None:
94 log.error('Could not found repo %s or %s' % (org_repo, other_repo))
99 log.error('Could not found repo %s or %s' % (org_repo, other_repo))
95 raise HTTPNotFound
100 raise HTTPNotFound
96
101
97 if c.org_repo.scm_instance.alias != 'hg':
102 if c.org_repo.scm_instance.alias != 'hg':
98 log.error('Review not available for GIT REPOS')
103 log.error('Review not available for GIT REPOS')
99 raise HTTPNotFound
104 raise HTTPNotFound
100
105 partial = request.environ.get('HTTP_X_PARTIAL_XHR')
101 self.__get_cs_or_redirect(rev=org_ref, repo=org_repo)
106 self.__get_cs_or_redirect(rev=org_ref, repo=org_repo, partial=partial)
102 self.__get_cs_or_redirect(rev=other_ref, repo=other_repo)
107 self.__get_cs_or_redirect(rev=other_ref, repo=other_repo, partial=partial)
103
108
104 c.cs_ranges, discovery_data = PullRequestModel().get_compare_data(
109 c.cs_ranges, discovery_data = PullRequestModel().get_compare_data(
105 org_repo, org_ref, other_repo, other_ref
110 org_repo, org_ref, other_repo, other_ref
106 )
111 )
107
112
108 c.statuses = c.rhodecode_db_repo.statuses([x.raw_id for x in
113 c.statuses = c.rhodecode_db_repo.statuses([x.raw_id for x in
109 c.cs_ranges])
114 c.cs_ranges])
110 c.target_repo = c.repo_name
115 c.target_repo = c.repo_name
111 # defines that we need hidden inputs with changesets
116 # defines that we need hidden inputs with changesets
112 c.as_form = request.GET.get('as_form', False)
117 c.as_form = request.GET.get('as_form', False)
113 if request.environ.get('HTTP_X_PARTIAL_XHR'):
118 if partial:
114 return render('compare/compare_cs.html')
119 return render('compare/compare_cs.html')
115
120
116 c.org_ref = org_ref[1]
121 c.org_ref = org_ref[1]
117 c.other_ref = other_ref[1]
122 c.other_ref = other_ref[1]
118 # diff needs to have swapped org with other to generate proper diff
123 # diff needs to have swapped org with other to generate proper diff
119 _diff = diffs.differ(other_repo, other_ref, org_repo, org_ref,
124 _diff = diffs.differ(other_repo, other_ref, org_repo, org_ref,
120 discovery_data)
125 discovery_data)
121 diff_processor = diffs.DiffProcessor(_diff, format='gitdiff')
126 diff_processor = diffs.DiffProcessor(_diff, format='gitdiff')
122 _parsed = diff_processor.prepare()
127 _parsed = diff_processor.prepare()
123
128
124 c.files = []
129 c.files = []
125 c.changes = {}
130 c.changes = {}
126
131
127 for f in _parsed:
132 for f in _parsed:
128 fid = h.FID('', f['filename'])
133 fid = h.FID('', f['filename'])
129 c.files.append([fid, f['operation'], f['filename'], f['stats']])
134 c.files.append([fid, f['operation'], f['filename'], f['stats']])
130 diff = diff_processor.as_html(enable_comments=False, diff_lines=[f])
135 diff = diff_processor.as_html(enable_comments=False, diff_lines=[f])
131 c.changes[fid] = [f['operation'], f['filename'], diff]
136 c.changes[fid] = [f['operation'], f['filename'], diff]
132
137
133 return render('compare/compare_diff.html')
138 return render('compare/compare_diff.html')
@@ -1,57 +1,58 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.followers
3 rhodecode.controllers.followers
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Followers controller for rhodecode
6 Followers controller for rhodecode
7
7
8 :created_on: Apr 23, 2011
8 :created_on: Apr 23, 2011
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2011-2012 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 logging
25 import logging
26
26
27 from pylons import tmpl_context as c, request
27 from pylons import tmpl_context as c, request
28
28
29 from rhodecode.lib.helpers import Page
29 from rhodecode.lib.helpers import Page
30 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
30 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
31 from rhodecode.lib.base import BaseRepoController, render
31 from rhodecode.lib.base import BaseRepoController, render
32 from rhodecode.model.db import Repository, User, UserFollowing
32 from rhodecode.model.db import Repository, User, UserFollowing
33 from rhodecode.lib.utils2 import safe_int
33
34
34 log = logging.getLogger(__name__)
35 log = logging.getLogger(__name__)
35
36
36
37
37 class FollowersController(BaseRepoController):
38 class FollowersController(BaseRepoController):
38
39
39 @LoginRequired()
40 @LoginRequired()
40 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
41 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
41 'repository.admin')
42 'repository.admin')
42 def __before__(self):
43 def __before__(self):
43 super(FollowersController, self).__before__()
44 super(FollowersController, self).__before__()
44
45
45 def followers(self, repo_name):
46 def followers(self, repo_name):
46 p = int(request.params.get('page', 1))
47 p = safe_int(request.params.get('page', 1), 1)
47 repo_id = c.rhodecode_db_repo.repo_id
48 repo_id = c.rhodecode_db_repo.repo_id
48 d = UserFollowing.get_repo_followers(repo_id)\
49 d = UserFollowing.get_repo_followers(repo_id)\
49 .order_by(UserFollowing.follows_from)
50 .order_by(UserFollowing.follows_from)
50 c.followers_pager = Page(d, page=p, items_per_page=20)
51 c.followers_pager = Page(d, page=p, items_per_page=20)
51
52
52 c.followers_data = render('/followers/followers_data.html')
53 c.followers_data = render('/followers/followers_data.html')
53
54
54 if request.environ.get('HTTP_X_PARTIAL_XHR'):
55 if request.environ.get('HTTP_X_PARTIAL_XHR'):
55 return c.followers_data
56 return c.followers_data
56
57
57 return render('/followers/followers.html')
58 return render('/followers/followers.html')
@@ -1,184 +1,185 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.forks
3 rhodecode.controllers.forks
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 forks controller for rhodecode
6 forks controller for rhodecode
7
7
8 :created_on: Apr 23, 2011
8 :created_on: Apr 23, 2011
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2011-2012 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 logging
25 import logging
26 import formencode
26 import formencode
27 import traceback
27 import traceback
28 from formencode import htmlfill
28 from formencode import htmlfill
29
29
30 from pylons import tmpl_context as c, request, url
30 from pylons import tmpl_context as c, request, url
31 from pylons.controllers.util import redirect
31 from pylons.controllers.util import redirect
32 from pylons.i18n.translation import _
32 from pylons.i18n.translation import _
33
33
34 import rhodecode.lib.helpers as h
34 import rhodecode.lib.helpers as h
35
35
36 from rhodecode.lib.helpers import Page
36 from rhodecode.lib.helpers import Page
37 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator, \
37 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator, \
38 NotAnonymous, HasRepoPermissionAny, HasPermissionAllDecorator,\
38 NotAnonymous, HasRepoPermissionAny, HasPermissionAllDecorator,\
39 HasPermissionAnyDecorator
39 HasPermissionAnyDecorator
40 from rhodecode.lib.base import BaseRepoController, render
40 from rhodecode.lib.base import BaseRepoController, render
41 from rhodecode.model.db import Repository, RepoGroup, UserFollowing, User
41 from rhodecode.model.db import Repository, RepoGroup, UserFollowing, User
42 from rhodecode.model.repo import RepoModel
42 from rhodecode.model.repo import RepoModel
43 from rhodecode.model.forms import RepoForkForm
43 from rhodecode.model.forms import RepoForkForm
44 from rhodecode.model.scm import ScmModel
44 from rhodecode.model.scm import ScmModel
45 from rhodecode.lib.utils2 import safe_int
45
46
46 log = logging.getLogger(__name__)
47 log = logging.getLogger(__name__)
47
48
48
49
49 class ForksController(BaseRepoController):
50 class ForksController(BaseRepoController):
50
51
51 @LoginRequired()
52 @LoginRequired()
52 def __before__(self):
53 def __before__(self):
53 super(ForksController, self).__before__()
54 super(ForksController, self).__before__()
54
55
55 def __load_defaults(self):
56 def __load_defaults(self):
56 c.repo_groups = RepoGroup.groups_choices()
57 c.repo_groups = RepoGroup.groups_choices(check_perms=True)
57 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
58 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
58 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
59 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
59 c.landing_revs_choices = choices
60 c.landing_revs_choices = choices
60
61
61 def __load_data(self, repo_name=None):
62 def __load_data(self, repo_name=None):
62 """
63 """
63 Load defaults settings for edit, and update
64 Load defaults settings for edit, and update
64
65
65 :param repo_name:
66 :param repo_name:
66 """
67 """
67 self.__load_defaults()
68 self.__load_defaults()
68
69
69 c.repo_info = db_repo = Repository.get_by_repo_name(repo_name)
70 c.repo_info = db_repo = Repository.get_by_repo_name(repo_name)
70 repo = db_repo.scm_instance
71 repo = db_repo.scm_instance
71
72
72 if c.repo_info is None:
73 if c.repo_info is None:
73 h.flash(_('%s repository is not mapped to db perhaps'
74 h.flash(_('%s repository is not mapped to db perhaps'
74 ' it was created or renamed from the filesystem'
75 ' it was created or renamed from the filesystem'
75 ' please run the application again'
76 ' please run the application again'
76 ' in order to rescan repositories') % repo_name,
77 ' in order to rescan repositories') % repo_name,
77 category='error')
78 category='error')
78
79
79 return redirect(url('repos'))
80 return redirect(url('repos'))
80
81
81 c.default_user_id = User.get_by_username('default').user_id
82 c.default_user_id = User.get_by_username('default').user_id
82 c.in_public_journal = UserFollowing.query()\
83 c.in_public_journal = UserFollowing.query()\
83 .filter(UserFollowing.user_id == c.default_user_id)\
84 .filter(UserFollowing.user_id == c.default_user_id)\
84 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
85 .filter(UserFollowing.follows_repository == c.repo_info).scalar()
85
86
86 if c.repo_info.stats:
87 if c.repo_info.stats:
87 last_rev = c.repo_info.stats.stat_on_revision+1
88 last_rev = c.repo_info.stats.stat_on_revision+1
88 else:
89 else:
89 last_rev = 0
90 last_rev = 0
90 c.stats_revision = last_rev
91 c.stats_revision = last_rev
91
92
92 c.repo_last_rev = repo.count() if repo.revisions else 0
93 c.repo_last_rev = repo.count() if repo.revisions else 0
93
94
94 if last_rev == 0 or c.repo_last_rev == 0:
95 if last_rev == 0 or c.repo_last_rev == 0:
95 c.stats_percentage = 0
96 c.stats_percentage = 0
96 else:
97 else:
97 c.stats_percentage = '%.2f' % ((float((last_rev)) /
98 c.stats_percentage = '%.2f' % ((float((last_rev)) /
98 c.repo_last_rev) * 100)
99 c.repo_last_rev) * 100)
99
100
100 defaults = RepoModel()._get_defaults(repo_name)
101 defaults = RepoModel()._get_defaults(repo_name)
101 # add prefix to fork
102 # add prefix to fork
102 defaults['repo_name'] = 'fork-' + defaults['repo_name']
103 defaults['repo_name'] = 'fork-' + defaults['repo_name']
103 return defaults
104 return defaults
104
105
105 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
106 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
106 'repository.admin')
107 'repository.admin')
107 def forks(self, repo_name):
108 def forks(self, repo_name):
108 p = int(request.params.get('page', 1))
109 p = safe_int(request.params.get('page', 1), 1)
109 repo_id = c.rhodecode_db_repo.repo_id
110 repo_id = c.rhodecode_db_repo.repo_id
110 d = []
111 d = []
111 for r in Repository.get_repo_forks(repo_id):
112 for r in Repository.get_repo_forks(repo_id):
112 if not HasRepoPermissionAny(
113 if not HasRepoPermissionAny(
113 'repository.read', 'repository.write', 'repository.admin'
114 'repository.read', 'repository.write', 'repository.admin'
114 )(r.repo_name, 'get forks check'):
115 )(r.repo_name, 'get forks check'):
115 continue
116 continue
116 d.append(r)
117 d.append(r)
117 c.forks_pager = Page(d, page=p, items_per_page=20)
118 c.forks_pager = Page(d, page=p, items_per_page=20)
118
119
119 c.forks_data = render('/forks/forks_data.html')
120 c.forks_data = render('/forks/forks_data.html')
120
121
121 if request.environ.get('HTTP_X_PARTIAL_XHR'):
122 if request.environ.get('HTTP_X_PARTIAL_XHR'):
122 return c.forks_data
123 return c.forks_data
123
124
124 return render('/forks/forks.html')
125 return render('/forks/forks.html')
125
126
126 @NotAnonymous()
127 @NotAnonymous()
127 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
128 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
128 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
129 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
129 'repository.admin')
130 'repository.admin')
130 def fork(self, repo_name):
131 def fork(self, repo_name):
131 c.repo_info = Repository.get_by_repo_name(repo_name)
132 c.repo_info = Repository.get_by_repo_name(repo_name)
132 if not c.repo_info:
133 if not c.repo_info:
133 h.flash(_('%s repository is not mapped to db perhaps'
134 h.flash(_('%s repository is not mapped to db perhaps'
134 ' it was created or renamed from the file system'
135 ' it was created or renamed from the file system'
135 ' please run the application again'
136 ' please run the application again'
136 ' in order to rescan repositories') % repo_name,
137 ' in order to rescan repositories') % repo_name,
137 category='error')
138 category='error')
138
139
139 return redirect(url('home'))
140 return redirect(url('home'))
140
141
141 defaults = self.__load_data(repo_name)
142 defaults = self.__load_data(repo_name)
142
143
143 return htmlfill.render(
144 return htmlfill.render(
144 render('forks/fork.html'),
145 render('forks/fork.html'),
145 defaults=defaults,
146 defaults=defaults,
146 encoding="UTF-8",
147 encoding="UTF-8",
147 force_defaults=False
148 force_defaults=False
148 )
149 )
149
150
150 @NotAnonymous()
151 @NotAnonymous()
151 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
152 @HasPermissionAnyDecorator('hg.admin', 'hg.fork.repository')
152 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
153 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
153 'repository.admin')
154 'repository.admin')
154 def fork_create(self, repo_name):
155 def fork_create(self, repo_name):
155 self.__load_defaults()
156 self.__load_defaults()
156 c.repo_info = Repository.get_by_repo_name(repo_name)
157 c.repo_info = Repository.get_by_repo_name(repo_name)
157 _form = RepoForkForm(old_data={'repo_type': c.repo_info.repo_type},
158 _form = RepoForkForm(old_data={'repo_type': c.repo_info.repo_type},
158 repo_groups=c.repo_groups_choices,
159 repo_groups=c.repo_groups_choices,
159 landing_revs=c.landing_revs_choices)()
160 landing_revs=c.landing_revs_choices)()
160 form_result = {}
161 form_result = {}
161 try:
162 try:
162 form_result = _form.to_python(dict(request.POST))
163 form_result = _form.to_python(dict(request.POST))
163
164
164 # create fork is done sometimes async on celery, db transaction
165 # create fork is done sometimes async on celery, db transaction
165 # management is handled there.
166 # management is handled there.
166 RepoModel().create_fork(form_result, self.rhodecode_user.user_id)
167 RepoModel().create_fork(form_result, self.rhodecode_user.user_id)
167 h.flash(_('forked %s repository as %s') \
168 h.flash(_('forked %s repository as %s') \
168 % (repo_name, form_result['repo_name']),
169 % (repo_name, form_result['repo_name']),
169 category='success')
170 category='success')
170 except formencode.Invalid, errors:
171 except formencode.Invalid, errors:
171 c.new_repo = errors.value['repo_name']
172 c.new_repo = errors.value['repo_name']
172
173
173 return htmlfill.render(
174 return htmlfill.render(
174 render('forks/fork.html'),
175 render('forks/fork.html'),
175 defaults=errors.value,
176 defaults=errors.value,
176 errors=errors.error_dict or {},
177 errors=errors.error_dict or {},
177 prefix_error=False,
178 prefix_error=False,
178 encoding="UTF-8")
179 encoding="UTF-8")
179 except Exception:
180 except Exception:
180 log.error(traceback.format_exc())
181 log.error(traceback.format_exc())
181 h.flash(_('An error occurred during repository forking %s') %
182 h.flash(_('An error occurred during repository forking %s') %
182 repo_name, category='error')
183 repo_name, category='error')
183
184
184 return redirect(url('home'))
185 return redirect(url('home'))
@@ -1,294 +1,295 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.journal
3 rhodecode.controllers.journal
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Journal controller for pylons
6 Journal controller for pylons
7
7
8 :created_on: Nov 21, 2010
8 :created_on: Nov 21, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 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 logging
25 import logging
26 from itertools import groupby
26 from itertools import groupby
27
27
28 from sqlalchemy import or_
28 from sqlalchemy import or_
29 from sqlalchemy.orm import joinedload
29 from sqlalchemy.orm import joinedload
30 from webhelpers.paginate import Page
30 from webhelpers.paginate import Page
31 from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed
31 from webhelpers.feedgenerator import Atom1Feed, Rss201rev2Feed
32
32
33 from webob.exc import HTTPBadRequest
33 from webob.exc import HTTPBadRequest
34 from pylons import request, tmpl_context as c, response, url
34 from pylons import request, tmpl_context as c, response, url
35 from pylons.i18n.translation import _
35 from pylons.i18n.translation import _
36
36
37 import rhodecode.lib.helpers as h
37 import rhodecode.lib.helpers as h
38 from rhodecode.lib.auth import LoginRequired, NotAnonymous
38 from rhodecode.lib.auth import LoginRequired, NotAnonymous
39 from rhodecode.lib.base import BaseController, render
39 from rhodecode.lib.base import BaseController, render
40 from rhodecode.model.db import UserLog, UserFollowing, Repository, User
40 from rhodecode.model.db import UserLog, UserFollowing, Repository, User
41 from rhodecode.model.meta import Session
41 from rhodecode.model.meta import Session
42 from sqlalchemy.sql.expression import func
42 from sqlalchemy.sql.expression import func
43 from rhodecode.model.scm import ScmModel
43 from rhodecode.model.scm import ScmModel
44 from rhodecode.lib.utils2 import safe_int
44
45
45 log = logging.getLogger(__name__)
46 log = logging.getLogger(__name__)
46
47
47
48
48 class JournalController(BaseController):
49 class JournalController(BaseController):
49
50
50 def __before__(self):
51 def __before__(self):
51 super(JournalController, self).__before__()
52 super(JournalController, self).__before__()
52 self.language = 'en-us'
53 self.language = 'en-us'
53 self.ttl = "5"
54 self.ttl = "5"
54 self.feed_nr = 20
55 self.feed_nr = 20
55
56
56 @LoginRequired()
57 @LoginRequired()
57 @NotAnonymous()
58 @NotAnonymous()
58 def index(self):
59 def index(self):
59 # Return a rendered template
60 # Return a rendered template
60 p = int(request.params.get('page', 1))
61 p = safe_int(request.params.get('page', 1), 1)
61
62
62 c.user = User.get(self.rhodecode_user.user_id)
63 c.user = User.get(self.rhodecode_user.user_id)
63 all_repos = self.sa.query(Repository)\
64 all_repos = self.sa.query(Repository)\
64 .filter(Repository.user_id == c.user.user_id)\
65 .filter(Repository.user_id == c.user.user_id)\
65 .order_by(func.lower(Repository.repo_name)).all()
66 .order_by(func.lower(Repository.repo_name)).all()
66
67
67 c.user_repos = ScmModel().get_repos(all_repos)
68 c.user_repos = ScmModel().get_repos(all_repos)
68
69
69 c.following = self.sa.query(UserFollowing)\
70 c.following = self.sa.query(UserFollowing)\
70 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
71 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
71 .options(joinedload(UserFollowing.follows_repository))\
72 .options(joinedload(UserFollowing.follows_repository))\
72 .all()
73 .all()
73
74
74 journal = self._get_journal_data(c.following)
75 journal = self._get_journal_data(c.following)
75
76
76 c.journal_pager = Page(journal, page=p, items_per_page=20)
77 c.journal_pager = Page(journal, page=p, items_per_page=20)
77
78
78 c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
79 c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
79
80
80 c.journal_data = render('journal/journal_data.html')
81 c.journal_data = render('journal/journal_data.html')
81 if request.environ.get('HTTP_X_PARTIAL_XHR'):
82 if request.environ.get('HTTP_X_PARTIAL_XHR'):
82 return c.journal_data
83 return c.journal_data
83 return render('journal/journal.html')
84 return render('journal/journal.html')
84
85
85 @LoginRequired(api_access=True)
86 @LoginRequired(api_access=True)
86 @NotAnonymous()
87 @NotAnonymous()
87 def journal_atom(self):
88 def journal_atom(self):
88 """
89 """
89 Produce an atom-1.0 feed via feedgenerator module
90 Produce an atom-1.0 feed via feedgenerator module
90 """
91 """
91 following = self.sa.query(UserFollowing)\
92 following = self.sa.query(UserFollowing)\
92 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
93 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
93 .options(joinedload(UserFollowing.follows_repository))\
94 .options(joinedload(UserFollowing.follows_repository))\
94 .all()
95 .all()
95 return self._atom_feed(following, public=False)
96 return self._atom_feed(following, public=False)
96
97
97 @LoginRequired(api_access=True)
98 @LoginRequired(api_access=True)
98 @NotAnonymous()
99 @NotAnonymous()
99 def journal_rss(self):
100 def journal_rss(self):
100 """
101 """
101 Produce an rss feed via feedgenerator module
102 Produce an rss feed via feedgenerator module
102 """
103 """
103 following = self.sa.query(UserFollowing)\
104 following = self.sa.query(UserFollowing)\
104 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
105 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
105 .options(joinedload(UserFollowing.follows_repository))\
106 .options(joinedload(UserFollowing.follows_repository))\
106 .all()
107 .all()
107 return self._rss_feed(following, public=False)
108 return self._rss_feed(following, public=False)
108
109
109 def _get_daily_aggregate(self, journal):
110 def _get_daily_aggregate(self, journal):
110 groups = []
111 groups = []
111 for k, g in groupby(journal, lambda x: x.action_as_day):
112 for k, g in groupby(journal, lambda x: x.action_as_day):
112 user_group = []
113 user_group = []
113 for k2, g2 in groupby(list(g), lambda x: x.user.email):
114 for k2, g2 in groupby(list(g), lambda x: x.user.email):
114 l = list(g2)
115 l = list(g2)
115 user_group.append((l[0].user, l))
116 user_group.append((l[0].user, l))
116
117
117 groups.append((k, user_group,))
118 groups.append((k, user_group,))
118
119
119 return groups
120 return groups
120
121
121 def _get_journal_data(self, following_repos):
122 def _get_journal_data(self, following_repos):
122 repo_ids = [x.follows_repository.repo_id for x in following_repos
123 repo_ids = [x.follows_repository.repo_id for x in following_repos
123 if x.follows_repository is not None]
124 if x.follows_repository is not None]
124 user_ids = [x.follows_user.user_id for x in following_repos
125 user_ids = [x.follows_user.user_id for x in following_repos
125 if x.follows_user is not None]
126 if x.follows_user is not None]
126
127
127 filtering_criterion = None
128 filtering_criterion = None
128
129
129 if repo_ids and user_ids:
130 if repo_ids and user_ids:
130 filtering_criterion = or_(UserLog.repository_id.in_(repo_ids),
131 filtering_criterion = or_(UserLog.repository_id.in_(repo_ids),
131 UserLog.user_id.in_(user_ids))
132 UserLog.user_id.in_(user_ids))
132 if repo_ids and not user_ids:
133 if repo_ids and not user_ids:
133 filtering_criterion = UserLog.repository_id.in_(repo_ids)
134 filtering_criterion = UserLog.repository_id.in_(repo_ids)
134 if not repo_ids and user_ids:
135 if not repo_ids and user_ids:
135 filtering_criterion = UserLog.user_id.in_(user_ids)
136 filtering_criterion = UserLog.user_id.in_(user_ids)
136 if filtering_criterion is not None:
137 if filtering_criterion is not None:
137 journal = self.sa.query(UserLog)\
138 journal = self.sa.query(UserLog)\
138 .options(joinedload(UserLog.user))\
139 .options(joinedload(UserLog.user))\
139 .options(joinedload(UserLog.repository))\
140 .options(joinedload(UserLog.repository))\
140 .filter(filtering_criterion)\
141 .filter(filtering_criterion)\
141 .order_by(UserLog.action_date.desc())
142 .order_by(UserLog.action_date.desc())
142 else:
143 else:
143 journal = []
144 journal = []
144
145
145 return journal
146 return journal
146
147
147 @LoginRequired()
148 @LoginRequired()
148 @NotAnonymous()
149 @NotAnonymous()
149 def toggle_following(self):
150 def toggle_following(self):
150 cur_token = request.POST.get('auth_token')
151 cur_token = request.POST.get('auth_token')
151 token = h.get_token()
152 token = h.get_token()
152 if cur_token == token:
153 if cur_token == token:
153
154
154 user_id = request.POST.get('follows_user_id')
155 user_id = request.POST.get('follows_user_id')
155 if user_id:
156 if user_id:
156 try:
157 try:
157 self.scm_model.toggle_following_user(user_id,
158 self.scm_model.toggle_following_user(user_id,
158 self.rhodecode_user.user_id)
159 self.rhodecode_user.user_id)
159 Session.commit()
160 Session.commit()
160 return 'ok'
161 return 'ok'
161 except:
162 except:
162 raise HTTPBadRequest()
163 raise HTTPBadRequest()
163
164
164 repo_id = request.POST.get('follows_repo_id')
165 repo_id = request.POST.get('follows_repo_id')
165 if repo_id:
166 if repo_id:
166 try:
167 try:
167 self.scm_model.toggle_following_repo(repo_id,
168 self.scm_model.toggle_following_repo(repo_id,
168 self.rhodecode_user.user_id)
169 self.rhodecode_user.user_id)
169 Session.commit()
170 Session.commit()
170 return 'ok'
171 return 'ok'
171 except:
172 except:
172 raise HTTPBadRequest()
173 raise HTTPBadRequest()
173
174
174 log.debug('token mismatch %s vs %s' % (cur_token, token))
175 log.debug('token mismatch %s vs %s' % (cur_token, token))
175 raise HTTPBadRequest()
176 raise HTTPBadRequest()
176
177
177 @LoginRequired()
178 @LoginRequired()
178 def public_journal(self):
179 def public_journal(self):
179 # Return a rendered template
180 # Return a rendered template
180 p = int(request.params.get('page', 1))
181 p = safe_int(request.params.get('page', 1), 1)
181
182
182 c.following = self.sa.query(UserFollowing)\
183 c.following = self.sa.query(UserFollowing)\
183 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
184 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
184 .options(joinedload(UserFollowing.follows_repository))\
185 .options(joinedload(UserFollowing.follows_repository))\
185 .all()
186 .all()
186
187
187 journal = self._get_journal_data(c.following)
188 journal = self._get_journal_data(c.following)
188
189
189 c.journal_pager = Page(journal, page=p, items_per_page=20)
190 c.journal_pager = Page(journal, page=p, items_per_page=20)
190
191
191 c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
192 c.journal_day_aggreagate = self._get_daily_aggregate(c.journal_pager)
192
193
193 c.journal_data = render('journal/journal_data.html')
194 c.journal_data = render('journal/journal_data.html')
194 if request.environ.get('HTTP_X_PARTIAL_XHR'):
195 if request.environ.get('HTTP_X_PARTIAL_XHR'):
195 return c.journal_data
196 return c.journal_data
196 return render('journal/public_journal.html')
197 return render('journal/public_journal.html')
197
198
198 def _atom_feed(self, repos, public=True):
199 def _atom_feed(self, repos, public=True):
199 journal = self._get_journal_data(repos)
200 journal = self._get_journal_data(repos)
200 if public:
201 if public:
201 _link = url('public_journal_atom', qualified=True)
202 _link = url('public_journal_atom', qualified=True)
202 _desc = '%s %s %s' % (c.rhodecode_name, _('public journal'),
203 _desc = '%s %s %s' % (c.rhodecode_name, _('public journal'),
203 'atom feed')
204 'atom feed')
204 else:
205 else:
205 _link = url('journal_atom', qualified=True)
206 _link = url('journal_atom', qualified=True)
206 _desc = '%s %s %s' % (c.rhodecode_name, _('journal'), 'atom feed')
207 _desc = '%s %s %s' % (c.rhodecode_name, _('journal'), 'atom feed')
207
208
208 feed = Atom1Feed(title=_desc,
209 feed = Atom1Feed(title=_desc,
209 link=_link,
210 link=_link,
210 description=_desc,
211 description=_desc,
211 language=self.language,
212 language=self.language,
212 ttl=self.ttl)
213 ttl=self.ttl)
213
214
214 for entry in journal[:self.feed_nr]:
215 for entry in journal[:self.feed_nr]:
215 action, action_extra, ico = h.action_parser(entry, feed=True)
216 action, action_extra, ico = h.action_parser(entry, feed=True)
216 title = "%s - %s %s" % (entry.user.short_contact, action(),
217 title = "%s - %s %s" % (entry.user.short_contact, action(),
217 entry.repository.repo_name)
218 entry.repository.repo_name)
218 desc = action_extra()
219 desc = action_extra()
219 _url = None
220 _url = None
220 if entry.repository is not None:
221 if entry.repository is not None:
221 _url = url('changelog_home',
222 _url = url('changelog_home',
222 repo_name=entry.repository.repo_name,
223 repo_name=entry.repository.repo_name,
223 qualified=True)
224 qualified=True)
224
225
225 feed.add_item(title=title,
226 feed.add_item(title=title,
226 pubdate=entry.action_date,
227 pubdate=entry.action_date,
227 link=_url or url('', qualified=True),
228 link=_url or url('', qualified=True),
228 author_email=entry.user.email,
229 author_email=entry.user.email,
229 author_name=entry.user.full_contact,
230 author_name=entry.user.full_contact,
230 description=desc)
231 description=desc)
231
232
232 response.content_type = feed.mime_type
233 response.content_type = feed.mime_type
233 return feed.writeString('utf-8')
234 return feed.writeString('utf-8')
234
235
235 def _rss_feed(self, repos, public=True):
236 def _rss_feed(self, repos, public=True):
236 journal = self._get_journal_data(repos)
237 journal = self._get_journal_data(repos)
237 if public:
238 if public:
238 _link = url('public_journal_atom', qualified=True)
239 _link = url('public_journal_atom', qualified=True)
239 _desc = '%s %s %s' % (c.rhodecode_name, _('public journal'),
240 _desc = '%s %s %s' % (c.rhodecode_name, _('public journal'),
240 'rss feed')
241 'rss feed')
241 else:
242 else:
242 _link = url('journal_atom', qualified=True)
243 _link = url('journal_atom', qualified=True)
243 _desc = '%s %s %s' % (c.rhodecode_name, _('journal'), 'rss feed')
244 _desc = '%s %s %s' % (c.rhodecode_name, _('journal'), 'rss feed')
244
245
245 feed = Rss201rev2Feed(title=_desc,
246 feed = Rss201rev2Feed(title=_desc,
246 link=_link,
247 link=_link,
247 description=_desc,
248 description=_desc,
248 language=self.language,
249 language=self.language,
249 ttl=self.ttl)
250 ttl=self.ttl)
250
251
251 for entry in journal[:self.feed_nr]:
252 for entry in journal[:self.feed_nr]:
252 action, action_extra, ico = h.action_parser(entry, feed=True)
253 action, action_extra, ico = h.action_parser(entry, feed=True)
253 title = "%s - %s %s" % (entry.user.short_contact, action(),
254 title = "%s - %s %s" % (entry.user.short_contact, action(),
254 entry.repository.repo_name)
255 entry.repository.repo_name)
255 desc = action_extra()
256 desc = action_extra()
256 _url = None
257 _url = None
257 if entry.repository is not None:
258 if entry.repository is not None:
258 _url = url('changelog_home',
259 _url = url('changelog_home',
259 repo_name=entry.repository.repo_name,
260 repo_name=entry.repository.repo_name,
260 qualified=True)
261 qualified=True)
261
262
262 feed.add_item(title=title,
263 feed.add_item(title=title,
263 pubdate=entry.action_date,
264 pubdate=entry.action_date,
264 link=_url or url('', qualified=True),
265 link=_url or url('', qualified=True),
265 author_email=entry.user.email,
266 author_email=entry.user.email,
266 author_name=entry.user.full_contact,
267 author_name=entry.user.full_contact,
267 description=desc)
268 description=desc)
268
269
269 response.content_type = feed.mime_type
270 response.content_type = feed.mime_type
270 return feed.writeString('utf-8')
271 return feed.writeString('utf-8')
271
272
272 @LoginRequired(api_access=True)
273 @LoginRequired(api_access=True)
273 def public_journal_atom(self):
274 def public_journal_atom(self):
274 """
275 """
275 Produce an atom-1.0 feed via feedgenerator module
276 Produce an atom-1.0 feed via feedgenerator module
276 """
277 """
277 c.following = self.sa.query(UserFollowing)\
278 c.following = self.sa.query(UserFollowing)\
278 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
279 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
279 .options(joinedload(UserFollowing.follows_repository))\
280 .options(joinedload(UserFollowing.follows_repository))\
280 .all()
281 .all()
281
282
282 return self._atom_feed(c.following)
283 return self._atom_feed(c.following)
283
284
284 @LoginRequired(api_access=True)
285 @LoginRequired(api_access=True)
285 def public_journal_rss(self):
286 def public_journal_rss(self):
286 """
287 """
287 Produce an rss2 feed via feedgenerator module
288 Produce an rss2 feed via feedgenerator module
288 """
289 """
289 c.following = self.sa.query(UserFollowing)\
290 c.following = self.sa.query(UserFollowing)\
290 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
291 .filter(UserFollowing.user_id == self.rhodecode_user.user_id)\
291 .options(joinedload(UserFollowing.follows_repository))\
292 .options(joinedload(UserFollowing.follows_repository))\
292 .all()
293 .all()
293
294
294 return self._rss_feed(c.following)
295 return self._rss_feed(c.following)
@@ -1,415 +1,431 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.pullrequests
3 rhodecode.controllers.pullrequests
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 pull requests controller for rhodecode for initializing pull requests
6 pull requests controller for rhodecode for initializing pull requests
7
7
8 :created_on: May 7, 2012
8 :created_on: May 7, 2012
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 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 logging
25 import logging
26 import traceback
26 import traceback
27 import formencode
27 import formencode
28
28
29 from webob.exc import HTTPNotFound, HTTPForbidden
29 from webob.exc import HTTPNotFound, HTTPForbidden
30 from collections import defaultdict
30 from collections import defaultdict
31 from itertools import groupby
31 from itertools import groupby
32
32
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 from pylons.decorators import jsonify
36 from pylons.decorators import jsonify
37
37
38 from rhodecode.lib.compat import json
38 from rhodecode.lib.compat import json
39 from rhodecode.lib.base import BaseRepoController, render
39 from rhodecode.lib.base import BaseRepoController, render
40 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator,\
40 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator,\
41 NotAnonymous
41 NotAnonymous
42 from rhodecode.lib import helpers as h
42 from rhodecode.lib import helpers as h
43 from rhodecode.lib import diffs
43 from rhodecode.lib import diffs
44 from rhodecode.lib.utils import action_logger
44 from rhodecode.lib.utils import action_logger
45 from rhodecode.model.db import User, PullRequest, ChangesetStatus,\
45 from rhodecode.model.db import User, PullRequest, ChangesetStatus,\
46 ChangesetComment
46 ChangesetComment
47 from rhodecode.model.pull_request import PullRequestModel
47 from rhodecode.model.pull_request import PullRequestModel
48 from rhodecode.model.meta import Session
48 from rhodecode.model.meta import Session
49 from rhodecode.model.repo import RepoModel
49 from rhodecode.model.repo import RepoModel
50 from rhodecode.model.comment import ChangesetCommentsModel
50 from rhodecode.model.comment import ChangesetCommentsModel
51 from rhodecode.model.changeset_status import ChangesetStatusModel
51 from rhodecode.model.changeset_status import ChangesetStatusModel
52 from rhodecode.model.forms import PullRequestForm
52 from rhodecode.model.forms import PullRequestForm
53
53
54 log = logging.getLogger(__name__)
54 log = logging.getLogger(__name__)
55
55
56
56
57 class PullrequestsController(BaseRepoController):
57 class PullrequestsController(BaseRepoController):
58
58
59 @LoginRequired()
59 @LoginRequired()
60 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
60 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
61 'repository.admin')
61 'repository.admin')
62 def __before__(self):
62 def __before__(self):
63 super(PullrequestsController, self).__before__()
63 super(PullrequestsController, self).__before__()
64 repo_model = RepoModel()
64 repo_model = RepoModel()
65 c.users_array = repo_model.get_users_js()
65 c.users_array = repo_model.get_users_js()
66 c.users_groups_array = repo_model.get_users_groups_js()
66 c.users_groups_array = repo_model.get_users_groups_js()
67
67
68 def _get_repo_refs(self, repo):
68 def _get_repo_refs(self, repo):
69 hist_l = []
69 hist_l = []
70
70
71 branches_group = ([('branch:%s:%s' % (k, v), k) for
71 branches_group = ([('branch:%s:%s' % (k, v), k) for
72 k, v in repo.branches.iteritems()], _("Branches"))
72 k, v in repo.branches.iteritems()], _("Branches"))
73 bookmarks_group = ([('book:%s:%s' % (k, v), k) for
73 bookmarks_group = ([('book:%s:%s' % (k, v), k) for
74 k, v in repo.bookmarks.iteritems()], _("Bookmarks"))
74 k, v in repo.bookmarks.iteritems()], _("Bookmarks"))
75 tags_group = ([('tag:%s:%s' % (k, v), k) for
75 tags_group = ([('tag:%s:%s' % (k, v), k) for
76 k, v in repo.tags.iteritems()], _("Tags"))
76 k, v in repo.tags.iteritems()], _("Tags"))
77
77
78 hist_l.append(bookmarks_group)
78 hist_l.append(bookmarks_group)
79 hist_l.append(branches_group)
79 hist_l.append(branches_group)
80 hist_l.append(tags_group)
80 hist_l.append(tags_group)
81
81
82 return hist_l
82 return hist_l
83
83
84 def _get_default_rev(self, repo):
85 """
86 Get's default revision to do compare on pull request
87
88 :param repo:
89 """
90 repo = repo.scm_instance
91 if 'default' in repo.branches:
92 return 'default'
93 else:
94 #if repo doesn't have default branch return first found
95 return repo.branches.keys()[0]
96
84 def show_all(self, repo_name):
97 def show_all(self, repo_name):
85 c.pull_requests = PullRequestModel().get_all(repo_name)
98 c.pull_requests = PullRequestModel().get_all(repo_name)
86 c.repo_name = repo_name
99 c.repo_name = repo_name
87 return render('/pullrequests/pullrequest_show_all.html')
100 return render('/pullrequests/pullrequest_show_all.html')
88
101
89 @NotAnonymous()
102 @NotAnonymous()
90 def index(self):
103 def index(self):
91 org_repo = c.rhodecode_db_repo
104 org_repo = c.rhodecode_db_repo
92
105
93 if org_repo.scm_instance.alias != 'hg':
106 if org_repo.scm_instance.alias != 'hg':
94 log.error('Review not available for GIT REPOS')
107 log.error('Review not available for GIT REPOS')
95 raise HTTPNotFound
108 raise HTTPNotFound
96
109
97 other_repos_info = {}
110 other_repos_info = {}
98
111
99 c.org_refs = self._get_repo_refs(c.rhodecode_repo)
112 c.org_refs = self._get_repo_refs(c.rhodecode_repo)
100 c.org_repos = []
113 c.org_repos = []
101 c.other_repos = []
114 c.other_repos = []
102 c.org_repos.append((org_repo.repo_name, '%s/%s' % (
115 c.org_repos.append((org_repo.repo_name, '%s/%s' % (
103 org_repo.user.username, c.repo_name))
116 org_repo.user.username, c.repo_name))
104 )
117 )
105
118
106 # add org repo to other so we can open pull request agains itself
119 # add org repo to other so we can open pull request agains itself
107 c.other_repos.extend(c.org_repos)
120 c.other_repos.extend(c.org_repos)
108
121
109 c.default_pull_request = org_repo.repo_name
122 c.default_pull_request = org_repo.repo_name # repo name pre-selected
123 c.default_pull_request_rev = self._get_default_rev(org_repo) # revision pre-selected
110 c.default_revs = self._get_repo_refs(org_repo.scm_instance)
124 c.default_revs = self._get_repo_refs(org_repo.scm_instance)
111 #add orginal repo
125 #add orginal repo
112 other_repos_info[org_repo.repo_name] = {
126 other_repos_info[org_repo.repo_name] = {
113 'gravatar': h.gravatar_url(org_repo.user.email, 24),
127 'gravatar': h.gravatar_url(org_repo.user.email, 24),
114 'description': org_repo.description,
128 'description': org_repo.description,
115 'revs': h.select('other_ref', '', c.default_revs, class_='refs')
129 'revs': h.select('other_ref', '', c.default_revs, class_='refs')
116 }
130 }
117
131
118 #gather forks and add to this list
132 #gather forks and add to this list
119 for fork in org_repo.forks:
133 for fork in org_repo.forks:
120 c.other_repos.append((fork.repo_name, '%s/%s' % (
134 c.other_repos.append((fork.repo_name, '%s/%s' % (
121 fork.user.username, fork.repo_name))
135 fork.user.username, fork.repo_name))
122 )
136 )
123 other_repos_info[fork.repo_name] = {
137 other_repos_info[fork.repo_name] = {
124 'gravatar': h.gravatar_url(fork.user.email, 24),
138 'gravatar': h.gravatar_url(fork.user.email, 24),
125 'description': fork.description,
139 'description': fork.description,
126 'revs': h.select('other_ref', '',
140 'revs': h.select('other_ref', '',
127 self._get_repo_refs(fork.scm_instance),
141 self._get_repo_refs(fork.scm_instance),
128 class_='refs')
142 class_='refs')
129 }
143 }
130 #add parents of this fork also
144 #add parents of this fork also
131 if org_repo.parent:
145 if org_repo.parent:
132 c.default_pull_request = org_repo.parent.repo_name
146 c.default_pull_request = org_repo.parent.repo_name
147 c.default_pull_request_rev = self._get_default_rev(org_repo.parent)
148 c.default_revs = self._get_repo_refs(org_repo.parent.scm_instance)
133 c.other_repos.append((org_repo.parent.repo_name, '%s/%s' % (
149 c.other_repos.append((org_repo.parent.repo_name, '%s/%s' % (
134 org_repo.parent.user.username,
150 org_repo.parent.user.username,
135 org_repo.parent.repo_name))
151 org_repo.parent.repo_name))
136 )
152 )
137 other_repos_info[org_repo.parent.repo_name] = {
153 other_repos_info[org_repo.parent.repo_name] = {
138 'gravatar': h.gravatar_url(org_repo.parent.user.email, 24),
154 'gravatar': h.gravatar_url(org_repo.parent.user.email, 24),
139 'description': org_repo.parent.description,
155 'description': org_repo.parent.description,
140 'revs': h.select('other_ref', '',
156 'revs': h.select('other_ref', '',
141 self._get_repo_refs(org_repo.parent.scm_instance),
157 self._get_repo_refs(org_repo.parent.scm_instance),
142 class_='refs')
158 class_='refs')
143 }
159 }
144
160
145 c.other_repos_info = json.dumps(other_repos_info)
161 c.other_repos_info = json.dumps(other_repos_info)
146 c.review_members = [org_repo.user]
162 c.review_members = [org_repo.user]
147 return render('/pullrequests/pullrequest.html')
163 return render('/pullrequests/pullrequest.html')
148
164
149 @NotAnonymous()
165 @NotAnonymous()
150 def create(self, repo_name):
166 def create(self, repo_name):
151 try:
167 try:
152 _form = PullRequestForm()().to_python(request.POST)
168 _form = PullRequestForm()().to_python(request.POST)
153 except formencode.Invalid, errors:
169 except formencode.Invalid, errors:
154 log.error(traceback.format_exc())
170 log.error(traceback.format_exc())
155 if errors.error_dict.get('revisions'):
171 if errors.error_dict.get('revisions'):
156 msg = 'Revisions: %s' % errors.error_dict['revisions']
172 msg = 'Revisions: %s' % errors.error_dict['revisions']
157 elif errors.error_dict.get('pullrequest_title'):
173 elif errors.error_dict.get('pullrequest_title'):
158 msg = _('Pull request requires a title with min. 3 chars')
174 msg = _('Pull request requires a title with min. 3 chars')
159 else:
175 else:
160 msg = _('error during creation of pull request')
176 msg = _('error during creation of pull request')
161
177
162 h.flash(msg, 'error')
178 h.flash(msg, 'error')
163 return redirect(url('pullrequest_home', repo_name=repo_name))
179 return redirect(url('pullrequest_home', repo_name=repo_name))
164
180
165 org_repo = _form['org_repo']
181 org_repo = _form['org_repo']
166 org_ref = _form['org_ref']
182 org_ref = _form['org_ref']
167 other_repo = _form['other_repo']
183 other_repo = _form['other_repo']
168 other_ref = _form['other_ref']
184 other_ref = _form['other_ref']
169 revisions = _form['revisions']
185 revisions = _form['revisions']
170 reviewers = _form['review_members']
186 reviewers = _form['review_members']
171
187
172 title = _form['pullrequest_title']
188 title = _form['pullrequest_title']
173 description = _form['pullrequest_desc']
189 description = _form['pullrequest_desc']
174
190
175 try:
191 try:
176 pull_request = PullRequestModel().create(
192 pull_request = PullRequestModel().create(
177 self.rhodecode_user.user_id, org_repo, org_ref, other_repo,
193 self.rhodecode_user.user_id, org_repo, org_ref, other_repo,
178 other_ref, revisions, reviewers, title, description
194 other_ref, revisions, reviewers, title, description
179 )
195 )
180 Session().commit()
196 Session().commit()
181 h.flash(_('Successfully opened new pull request'),
197 h.flash(_('Successfully opened new pull request'),
182 category='success')
198 category='success')
183 except Exception:
199 except Exception:
184 h.flash(_('Error occurred during sending pull request'),
200 h.flash(_('Error occurred during sending pull request'),
185 category='error')
201 category='error')
186 log.error(traceback.format_exc())
202 log.error(traceback.format_exc())
187 return redirect(url('pullrequest_home', repo_name=repo_name))
203 return redirect(url('pullrequest_home', repo_name=repo_name))
188
204
189 return redirect(url('pullrequest_show', repo_name=other_repo,
205 return redirect(url('pullrequest_show', repo_name=other_repo,
190 pull_request_id=pull_request.pull_request_id))
206 pull_request_id=pull_request.pull_request_id))
191
207
192 @NotAnonymous()
208 @NotAnonymous()
193 @jsonify
209 @jsonify
194 def update(self, repo_name, pull_request_id):
210 def update(self, repo_name, pull_request_id):
195 pull_request = PullRequest.get_or_404(pull_request_id)
211 pull_request = PullRequest.get_or_404(pull_request_id)
196 if pull_request.is_closed():
212 if pull_request.is_closed():
197 raise HTTPForbidden()
213 raise HTTPForbidden()
198 #only owner or admin can update it
214 #only owner or admin can update it
199 owner = pull_request.author.user_id == c.rhodecode_user.user_id
215 owner = pull_request.author.user_id == c.rhodecode_user.user_id
200 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
216 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
201 reviewers_ids = map(int, filter(lambda v: v not in [None, ''],
217 reviewers_ids = map(int, filter(lambda v: v not in [None, ''],
202 request.POST.get('reviewers_ids', '').split(',')))
218 request.POST.get('reviewers_ids', '').split(',')))
203
219
204 PullRequestModel().update_reviewers(pull_request_id, reviewers_ids)
220 PullRequestModel().update_reviewers(pull_request_id, reviewers_ids)
205 Session.commit()
221 Session.commit()
206 return True
222 return True
207 raise HTTPForbidden()
223 raise HTTPForbidden()
208
224
209 @NotAnonymous()
225 @NotAnonymous()
210 @jsonify
226 @jsonify
211 def delete(self, repo_name, pull_request_id):
227 def delete(self, repo_name, pull_request_id):
212 pull_request = PullRequest.get_or_404(pull_request_id)
228 pull_request = PullRequest.get_or_404(pull_request_id)
213 #only owner can delete it !
229 #only owner can delete it !
214 if pull_request.author.user_id == c.rhodecode_user.user_id:
230 if pull_request.author.user_id == c.rhodecode_user.user_id:
215 PullRequestModel().delete(pull_request)
231 PullRequestModel().delete(pull_request)
216 Session().commit()
232 Session().commit()
217 h.flash(_('Successfully deleted pull request'),
233 h.flash(_('Successfully deleted pull request'),
218 category='success')
234 category='success')
219 return redirect(url('admin_settings_my_account'))
235 return redirect(url('admin_settings_my_account'))
220 raise HTTPForbidden()
236 raise HTTPForbidden()
221
237
222 def _load_compare_data(self, pull_request, enable_comments=True):
238 def _load_compare_data(self, pull_request, enable_comments=True):
223 """
239 """
224 Load context data needed for generating compare diff
240 Load context data needed for generating compare diff
225
241
226 :param pull_request:
242 :param pull_request:
227 :type pull_request:
243 :type pull_request:
228 """
244 """
229
245
230 org_repo = pull_request.org_repo
246 org_repo = pull_request.org_repo
231 (org_ref_type,
247 (org_ref_type,
232 org_ref_name,
248 org_ref_name,
233 org_ref_rev) = pull_request.org_ref.split(':')
249 org_ref_rev) = pull_request.org_ref.split(':')
234
250
235 other_repo = pull_request.other_repo
251 other_repo = pull_request.other_repo
236 (other_ref_type,
252 (other_ref_type,
237 other_ref_name,
253 other_ref_name,
238 other_ref_rev) = pull_request.other_ref.split(':')
254 other_ref_rev) = pull_request.other_ref.split(':')
239
255
240 # despite opening revisions for bookmarks/branches/tags, we always
256 # despite opening revisions for bookmarks/branches/tags, we always
241 # convert this to rev to prevent changes after book or branch change
257 # convert this to rev to prevent changes after book or branch change
242 org_ref = ('rev', org_ref_rev)
258 org_ref = ('rev', org_ref_rev)
243 other_ref = ('rev', other_ref_rev)
259 other_ref = ('rev', other_ref_rev)
244
260
245 c.org_repo = org_repo
261 c.org_repo = org_repo
246 c.other_repo = other_repo
262 c.other_repo = other_repo
247
263
248 c.cs_ranges, discovery_data = PullRequestModel().get_compare_data(
264 c.cs_ranges, discovery_data = PullRequestModel().get_compare_data(
249 org_repo, org_ref, other_repo, other_ref
265 org_repo, org_ref, other_repo, other_ref
250 )
266 )
251
267
252 c.statuses = org_repo.statuses([x.raw_id for x in c.cs_ranges])
268 c.statuses = org_repo.statuses([x.raw_id for x in c.cs_ranges])
253 # defines that we need hidden inputs with changesets
269 # defines that we need hidden inputs with changesets
254 c.as_form = request.GET.get('as_form', False)
270 c.as_form = request.GET.get('as_form', False)
255
271
256 c.org_ref = org_ref[1]
272 c.org_ref = org_ref[1]
257 c.other_ref = other_ref[1]
273 c.other_ref = other_ref[1]
258 # diff needs to have swapped org with other to generate proper diff
274 # diff needs to have swapped org with other to generate proper diff
259 _diff = diffs.differ(other_repo, other_ref, org_repo, org_ref,
275 _diff = diffs.differ(other_repo, other_ref, org_repo, org_ref,
260 discovery_data)
276 discovery_data)
261 diff_processor = diffs.DiffProcessor(_diff, format='gitdiff')
277 diff_processor = diffs.DiffProcessor(_diff, format='gitdiff')
262 _parsed = diff_processor.prepare()
278 _parsed = diff_processor.prepare()
263
279
264 c.files = []
280 c.files = []
265 c.changes = {}
281 c.changes = {}
266
282
267 for f in _parsed:
283 for f in _parsed:
268 fid = h.FID('', f['filename'])
284 fid = h.FID('', f['filename'])
269 c.files.append([fid, f['operation'], f['filename'], f['stats']])
285 c.files.append([fid, f['operation'], f['filename'], f['stats']])
270 diff = diff_processor.as_html(enable_comments=enable_comments,
286 diff = diff_processor.as_html(enable_comments=enable_comments,
271 diff_lines=[f])
287 diff_lines=[f])
272 c.changes[fid] = [f['operation'], f['filename'], diff]
288 c.changes[fid] = [f['operation'], f['filename'], diff]
273
289
274 def show(self, repo_name, pull_request_id):
290 def show(self, repo_name, pull_request_id):
275 repo_model = RepoModel()
291 repo_model = RepoModel()
276 c.users_array = repo_model.get_users_js()
292 c.users_array = repo_model.get_users_js()
277 c.users_groups_array = repo_model.get_users_groups_js()
293 c.users_groups_array = repo_model.get_users_groups_js()
278 c.pull_request = PullRequest.get_or_404(pull_request_id)
294 c.pull_request = PullRequest.get_or_404(pull_request_id)
279 c.target_repo = c.pull_request.org_repo.repo_name
295 c.target_repo = c.pull_request.org_repo.repo_name
280
296
281 cc_model = ChangesetCommentsModel()
297 cc_model = ChangesetCommentsModel()
282 cs_model = ChangesetStatusModel()
298 cs_model = ChangesetStatusModel()
283 _cs_statuses = cs_model.get_statuses(c.pull_request.org_repo,
299 _cs_statuses = cs_model.get_statuses(c.pull_request.org_repo,
284 pull_request=c.pull_request,
300 pull_request=c.pull_request,
285 with_revisions=True)
301 with_revisions=True)
286
302
287 cs_statuses = defaultdict(list)
303 cs_statuses = defaultdict(list)
288 for st in _cs_statuses:
304 for st in _cs_statuses:
289 cs_statuses[st.author.username] += [st]
305 cs_statuses[st.author.username] += [st]
290
306
291 c.pull_request_reviewers = []
307 c.pull_request_reviewers = []
292 c.pull_request_pending_reviewers = []
308 c.pull_request_pending_reviewers = []
293 for o in c.pull_request.reviewers:
309 for o in c.pull_request.reviewers:
294 st = cs_statuses.get(o.user.username, None)
310 st = cs_statuses.get(o.user.username, None)
295 if st:
311 if st:
296 sorter = lambda k: k.version
312 sorter = lambda k: k.version
297 st = [(x, list(y)[0])
313 st = [(x, list(y)[0])
298 for x, y in (groupby(sorted(st, key=sorter), sorter))]
314 for x, y in (groupby(sorted(st, key=sorter), sorter))]
299 else:
315 else:
300 c.pull_request_pending_reviewers.append(o.user)
316 c.pull_request_pending_reviewers.append(o.user)
301 c.pull_request_reviewers.append([o.user, st])
317 c.pull_request_reviewers.append([o.user, st])
302
318
303 # pull_requests repo_name we opened it against
319 # pull_requests repo_name we opened it against
304 # ie. other_repo must match
320 # ie. other_repo must match
305 if repo_name != c.pull_request.other_repo.repo_name:
321 if repo_name != c.pull_request.other_repo.repo_name:
306 raise HTTPNotFound
322 raise HTTPNotFound
307
323
308 # load compare data into template context
324 # load compare data into template context
309 enable_comments = not c.pull_request.is_closed()
325 enable_comments = not c.pull_request.is_closed()
310 self._load_compare_data(c.pull_request, enable_comments=enable_comments)
326 self._load_compare_data(c.pull_request, enable_comments=enable_comments)
311
327
312 # inline comments
328 # inline comments
313 c.inline_cnt = 0
329 c.inline_cnt = 0
314 c.inline_comments = cc_model.get_inline_comments(
330 c.inline_comments = cc_model.get_inline_comments(
315 c.rhodecode_db_repo.repo_id,
331 c.rhodecode_db_repo.repo_id,
316 pull_request=pull_request_id)
332 pull_request=pull_request_id)
317 # count inline comments
333 # count inline comments
318 for __, lines in c.inline_comments:
334 for __, lines in c.inline_comments:
319 for comments in lines.values():
335 for comments in lines.values():
320 c.inline_cnt += len(comments)
336 c.inline_cnt += len(comments)
321 # comments
337 # comments
322 c.comments = cc_model.get_comments(c.rhodecode_db_repo.repo_id,
338 c.comments = cc_model.get_comments(c.rhodecode_db_repo.repo_id,
323 pull_request=pull_request_id)
339 pull_request=pull_request_id)
324
340
325 try:
341 try:
326 cur_status = c.statuses[c.pull_request.revisions[0]][0]
342 cur_status = c.statuses[c.pull_request.revisions[0]][0]
327 except:
343 except:
328 log.error(traceback.format_exc())
344 log.error(traceback.format_exc())
329 cur_status = 'undefined'
345 cur_status = 'undefined'
330 if c.pull_request.is_closed() and 0:
346 if c.pull_request.is_closed() and 0:
331 c.current_changeset_status = cur_status
347 c.current_changeset_status = cur_status
332 else:
348 else:
333 # changeset(pull-request) status calulation based on reviewers
349 # changeset(pull-request) status calulation based on reviewers
334 c.current_changeset_status = cs_model.calculate_status(
350 c.current_changeset_status = cs_model.calculate_status(
335 c.pull_request_reviewers,
351 c.pull_request_reviewers,
336 )
352 )
337 c.changeset_statuses = ChangesetStatus.STATUSES
353 c.changeset_statuses = ChangesetStatus.STATUSES
338
354
339 return render('/pullrequests/pullrequest_show.html')
355 return render('/pullrequests/pullrequest_show.html')
340
356
341 @NotAnonymous()
357 @NotAnonymous()
342 @jsonify
358 @jsonify
343 def comment(self, repo_name, pull_request_id):
359 def comment(self, repo_name, pull_request_id):
344 pull_request = PullRequest.get_or_404(pull_request_id)
360 pull_request = PullRequest.get_or_404(pull_request_id)
345 if pull_request.is_closed():
361 if pull_request.is_closed():
346 raise HTTPForbidden()
362 raise HTTPForbidden()
347
363
348 status = request.POST.get('changeset_status')
364 status = request.POST.get('changeset_status')
349 change_status = request.POST.get('change_changeset_status')
365 change_status = request.POST.get('change_changeset_status')
350 text = request.POST.get('text')
366 text = request.POST.get('text')
351 if status and change_status:
367 if status and change_status:
352 text = text or (_('Status change -> %s')
368 text = text or (_('Status change -> %s')
353 % ChangesetStatus.get_status_lbl(status))
369 % ChangesetStatus.get_status_lbl(status))
354 comm = ChangesetCommentsModel().create(
370 comm = ChangesetCommentsModel().create(
355 text=text,
371 text=text,
356 repo=c.rhodecode_db_repo.repo_id,
372 repo=c.rhodecode_db_repo.repo_id,
357 user=c.rhodecode_user.user_id,
373 user=c.rhodecode_user.user_id,
358 pull_request=pull_request_id,
374 pull_request=pull_request_id,
359 f_path=request.POST.get('f_path'),
375 f_path=request.POST.get('f_path'),
360 line_no=request.POST.get('line'),
376 line_no=request.POST.get('line'),
361 status_change=(ChangesetStatus.get_status_lbl(status)
377 status_change=(ChangesetStatus.get_status_lbl(status)
362 if status and change_status else None)
378 if status and change_status else None)
363 )
379 )
364
380
365 # get status if set !
381 # get status if set !
366 if status and change_status:
382 if status and change_status:
367 ChangesetStatusModel().set_status(
383 ChangesetStatusModel().set_status(
368 c.rhodecode_db_repo.repo_id,
384 c.rhodecode_db_repo.repo_id,
369 status,
385 status,
370 c.rhodecode_user.user_id,
386 c.rhodecode_user.user_id,
371 comm,
387 comm,
372 pull_request=pull_request_id
388 pull_request=pull_request_id
373 )
389 )
374 action_logger(self.rhodecode_user,
390 action_logger(self.rhodecode_user,
375 'user_commented_pull_request:%s' % pull_request_id,
391 'user_commented_pull_request:%s' % pull_request_id,
376 c.rhodecode_db_repo, self.ip_addr, self.sa)
392 c.rhodecode_db_repo, self.ip_addr, self.sa)
377
393
378 if request.POST.get('save_close'):
394 if request.POST.get('save_close'):
379 PullRequestModel().close_pull_request(pull_request_id)
395 PullRequestModel().close_pull_request(pull_request_id)
380 action_logger(self.rhodecode_user,
396 action_logger(self.rhodecode_user,
381 'user_closed_pull_request:%s' % pull_request_id,
397 'user_closed_pull_request:%s' % pull_request_id,
382 c.rhodecode_db_repo, self.ip_addr, self.sa)
398 c.rhodecode_db_repo, self.ip_addr, self.sa)
383
399
384 Session().commit()
400 Session().commit()
385
401
386 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
402 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
387 return redirect(h.url('pullrequest_show', repo_name=repo_name,
403 return redirect(h.url('pullrequest_show', repo_name=repo_name,
388 pull_request_id=pull_request_id))
404 pull_request_id=pull_request_id))
389
405
390 data = {
406 data = {
391 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
407 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
392 }
408 }
393 if comm:
409 if comm:
394 c.co = comm
410 c.co = comm
395 data.update(comm.get_dict())
411 data.update(comm.get_dict())
396 data.update({'rendered_text':
412 data.update({'rendered_text':
397 render('changeset/changeset_comment_block.html')})
413 render('changeset/changeset_comment_block.html')})
398
414
399 return data
415 return data
400
416
401 @NotAnonymous()
417 @NotAnonymous()
402 @jsonify
418 @jsonify
403 def delete_comment(self, repo_name, comment_id):
419 def delete_comment(self, repo_name, comment_id):
404 co = ChangesetComment.get(comment_id)
420 co = ChangesetComment.get(comment_id)
405 if co.pull_request.is_closed():
421 if co.pull_request.is_closed():
406 #don't allow deleting comments on closed pull request
422 #don't allow deleting comments on closed pull request
407 raise HTTPForbidden()
423 raise HTTPForbidden()
408
424
409 owner = lambda: co.author.user_id == c.rhodecode_user.user_id
425 owner = lambda: co.author.user_id == c.rhodecode_user.user_id
410 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
426 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
411 ChangesetCommentsModel().delete(comment=co)
427 ChangesetCommentsModel().delete(comment=co)
412 Session().commit()
428 Session().commit()
413 return True
429 return True
414 else:
430 else:
415 raise HTTPForbidden()
431 raise HTTPForbidden()
@@ -1,143 +1,144 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.search
3 rhodecode.controllers.search
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Search controller for RhodeCode
6 Search controller for RhodeCode
7
7
8 :created_on: Aug 7, 2010
8 :created_on: Aug 7, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 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 logging
25 import logging
26 import traceback
26 import traceback
27
27
28 from pylons.i18n.translation import _
28 from pylons.i18n.translation import _
29 from pylons import request, config, tmpl_context as c
29 from pylons import request, config, tmpl_context as c
30
30
31 from rhodecode.lib.auth import LoginRequired
31 from rhodecode.lib.auth import LoginRequired
32 from rhodecode.lib.base import BaseController, render
32 from rhodecode.lib.base import BaseController, render
33 from rhodecode.lib.indexers import CHGSETS_SCHEMA, SCHEMA, CHGSET_IDX_NAME, \
33 from rhodecode.lib.indexers import CHGSETS_SCHEMA, SCHEMA, CHGSET_IDX_NAME, \
34 IDX_NAME, WhooshResultWrapper
34 IDX_NAME, WhooshResultWrapper
35
35
36 from webhelpers.paginate import Page
36 from webhelpers.paginate import Page
37 from webhelpers.util import update_params
37 from webhelpers.util import update_params
38
38
39 from whoosh.index import open_dir, EmptyIndexError
39 from whoosh.index import open_dir, EmptyIndexError
40 from whoosh.qparser import QueryParser, QueryParserError
40 from whoosh.qparser import QueryParser, QueryParserError
41 from whoosh.query import Phrase, Wildcard, Term, Prefix
41 from whoosh.query import Phrase, Wildcard, Term, Prefix
42 from rhodecode.model.repo import RepoModel
42 from rhodecode.model.repo import RepoModel
43 from rhodecode.lib.utils2 import safe_str, safe_int
43
44
44 log = logging.getLogger(__name__)
45 log = logging.getLogger(__name__)
45
46
46
47
47 class SearchController(BaseController):
48 class SearchController(BaseController):
48
49
49 @LoginRequired()
50 @LoginRequired()
50 def __before__(self):
51 def __before__(self):
51 super(SearchController, self).__before__()
52 super(SearchController, self).__before__()
52
53
53 def index(self, search_repo=None):
54 def index(self, search_repo=None):
54 c.repo_name = search_repo
55 c.repo_name = search_repo
55 c.formated_results = []
56 c.formated_results = []
56 c.runtime = ''
57 c.runtime = ''
57 c.cur_query = request.GET.get('q', None)
58 c.cur_query = request.GET.get('q', None)
58 c.cur_type = request.GET.get('type', 'content')
59 c.cur_type = request.GET.get('type', 'content')
59 c.cur_search = search_type = {'content': 'content',
60 c.cur_search = search_type = {'content': 'content',
60 'commit': 'message',
61 'commit': 'message',
61 'path': 'path',
62 'path': 'path',
62 'repository': 'repository'
63 'repository': 'repository'
63 }.get(c.cur_type, 'content')
64 }.get(c.cur_type, 'content')
64
65
65 index_name = {
66 index_name = {
66 'content': IDX_NAME,
67 'content': IDX_NAME,
67 'commit': CHGSET_IDX_NAME,
68 'commit': CHGSET_IDX_NAME,
68 'path': IDX_NAME
69 'path': IDX_NAME
69 }.get(c.cur_type, IDX_NAME)
70 }.get(c.cur_type, IDX_NAME)
70
71
71 schema_defn = {
72 schema_defn = {
72 'content': SCHEMA,
73 'content': SCHEMA,
73 'commit': CHGSETS_SCHEMA,
74 'commit': CHGSETS_SCHEMA,
74 'path': SCHEMA
75 'path': SCHEMA
75 }.get(c.cur_type, SCHEMA)
76 }.get(c.cur_type, SCHEMA)
76
77
77 log.debug('IDX: %s' % index_name)
78 log.debug('IDX: %s' % index_name)
78 log.debug('SCHEMA: %s' % schema_defn)
79 log.debug('SCHEMA: %s' % schema_defn)
79
80
80 if c.cur_query:
81 if c.cur_query:
81 cur_query = c.cur_query.lower()
82 cur_query = c.cur_query.lower()
82 log.debug(cur_query)
83 log.debug(cur_query)
83
84
84 if c.cur_query:
85 if c.cur_query:
85 p = int(request.params.get('page', 1))
86 p = safe_int(request.params.get('page', 1), 1)
86 highlight_items = set()
87 highlight_items = set()
87 try:
88 try:
88 idx = open_dir(config['app_conf']['index_dir'],
89 idx = open_dir(config['app_conf']['index_dir'],
89 indexname=index_name)
90 indexname=index_name)
90 searcher = idx.searcher()
91 searcher = idx.searcher()
91
92
92 qp = QueryParser(search_type, schema=schema_defn)
93 qp = QueryParser(search_type, schema=schema_defn)
93 if c.repo_name:
94 if c.repo_name:
94 cur_query = u'repository:%s %s' % (c.repo_name, cur_query)
95 cur_query = u'repository:%s %s' % (c.repo_name, cur_query)
95 try:
96 try:
96 query = qp.parse(unicode(cur_query))
97 query = qp.parse(unicode(cur_query))
97 # extract words for highlight
98 # extract words for highlight
98 if isinstance(query, Phrase):
99 if isinstance(query, Phrase):
99 highlight_items.update(query.words)
100 highlight_items.update(query.words)
100 elif isinstance(query, Prefix):
101 elif isinstance(query, Prefix):
101 highlight_items.add(query.text)
102 highlight_items.add(query.text)
102 else:
103 else:
103 for i in query.all_terms():
104 for i in query.all_terms():
104 if i[0] in ['content', 'message']:
105 if i[0] in ['content', 'message']:
105 highlight_items.add(i[1])
106 highlight_items.add(i[1])
106
107
107 matcher = query.matcher(searcher)
108 matcher = query.matcher(searcher)
108
109
109 log.debug('query: %s' % query)
110 log.debug('query: %s' % query)
110 log.debug('hl terms: %s' % highlight_items)
111 log.debug('hl terms: %s' % highlight_items)
111 results = searcher.search(query)
112 results = searcher.search(query)
112 res_ln = len(results)
113 res_ln = len(results)
113 c.runtime = '%s results (%.3f seconds)' % (
114 c.runtime = '%s results (%.3f seconds)' % (
114 res_ln, results.runtime
115 res_ln, results.runtime
115 )
116 )
116
117
117 def url_generator(**kw):
118 def url_generator(**kw):
118 return update_params("?q=%s&type=%s" \
119 return update_params("?q=%s&type=%s" \
119 % (c.cur_query, c.cur_type), **kw)
120 % (safe_str(c.cur_query), safe_str(c.cur_type)), **kw)
120 repo_location = RepoModel().repos_path
121 repo_location = RepoModel().repos_path
121 c.formated_results = Page(
122 c.formated_results = Page(
122 WhooshResultWrapper(search_type, searcher, matcher,
123 WhooshResultWrapper(search_type, searcher, matcher,
123 highlight_items, repo_location),
124 highlight_items, repo_location),
124 page=p,
125 page=p,
125 item_count=res_ln,
126 item_count=res_ln,
126 items_per_page=10,
127 items_per_page=10,
127 url=url_generator
128 url=url_generator
128 )
129 )
129
130
130 except QueryParserError:
131 except QueryParserError:
131 c.runtime = _('Invalid search query. Try quoting it.')
132 c.runtime = _('Invalid search query. Try quoting it.')
132 searcher.close()
133 searcher.close()
133 except (EmptyIndexError, IOError):
134 except (EmptyIndexError, IOError):
134 log.error(traceback.format_exc())
135 log.error(traceback.format_exc())
135 log.error('Empty Index data')
136 log.error('Empty Index data')
136 c.runtime = _('There is no index to search in. '
137 c.runtime = _('There is no index to search in. '
137 'Please run whoosh indexer')
138 'Please run whoosh indexer')
138 except (Exception):
139 except (Exception):
139 log.error(traceback.format_exc())
140 log.error(traceback.format_exc())
140 c.runtime = _('An error occurred during this search operation')
141 c.runtime = _('An error occurred during this search operation')
141
142
142 # Return a rendered template
143 # Return a rendered template
143 return render('/search/search.html')
144 return render('/search/search.html')
@@ -1,162 +1,191 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.settings
3 rhodecode.controllers.settings
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Settings controller for rhodecode
6 Settings controller for rhodecode
7
7
8 :created_on: Jun 30, 2010
8 :created_on: Jun 30, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 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 formencode import htmlfill
30 from formencode import htmlfill
31
31
32 from pylons import tmpl_context as c, request, url
32 from pylons import tmpl_context as c, request, url
33 from pylons.controllers.util import redirect
33 from pylons.controllers.util import redirect
34 from pylons.i18n.translation import _
34 from pylons.i18n.translation import _
35
35
36 import rhodecode.lib.helpers as h
36 import rhodecode.lib.helpers as h
37
37
38 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAllDecorator
38 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAllDecorator,\
39 HasRepoPermissionAnyDecorator
39 from rhodecode.lib.base import BaseRepoController, render
40 from rhodecode.lib.base import BaseRepoController, render
40 from rhodecode.lib.utils import invalidate_cache, action_logger
41 from rhodecode.lib.utils import invalidate_cache, action_logger
41
42
42 from rhodecode.model.forms import RepoSettingsForm
43 from rhodecode.model.forms import RepoSettingsForm
43 from rhodecode.model.repo import RepoModel
44 from rhodecode.model.repo import RepoModel
44 from rhodecode.model.db import RepoGroup
45 from rhodecode.model.db import RepoGroup, Repository
45 from rhodecode.model.meta import Session
46 from rhodecode.model.meta import Session
46 from rhodecode.model.scm import ScmModel
47 from rhodecode.model.scm import ScmModel
47
48
48 log = logging.getLogger(__name__)
49 log = logging.getLogger(__name__)
49
50
50
51
51 class SettingsController(BaseRepoController):
52 class SettingsController(BaseRepoController):
52
53
53 @LoginRequired()
54 @LoginRequired()
54 def __before__(self):
55 def __before__(self):
55 super(SettingsController, self).__before__()
56 super(SettingsController, self).__before__()
56
57
57 def __load_defaults(self):
58 def __load_defaults(self):
58 c.repo_groups = RepoGroup.groups_choices()
59 c.repo_groups = RepoGroup.groups_choices(check_perms=True)
59 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
60 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
60
61
61 repo_model = RepoModel()
62 repo_model = RepoModel()
62 c.users_array = repo_model.get_users_js()
63 c.users_array = repo_model.get_users_js()
63 c.users_groups_array = repo_model.get_users_groups_js()
64 c.users_groups_array = repo_model.get_users_groups_js()
64 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
65 choices, c.landing_revs = ScmModel().get_repo_landing_revs()
65 c.landing_revs_choices = choices
66 c.landing_revs_choices = choices
66
67
67 @HasRepoPermissionAllDecorator('repository.admin')
68 @HasRepoPermissionAllDecorator('repository.admin')
68 def index(self, repo_name):
69 def index(self, repo_name):
69 repo_model = RepoModel()
70 repo_model = RepoModel()
70 c.repo_info = repo = repo_model.get_by_repo_name(repo_name)
71 c.repo_info = repo = repo_model.get_by_repo_name(repo_name)
71 if not repo:
72 if not repo:
72 h.flash(_('%s repository is not mapped to db perhaps'
73 h.flash(_('%s repository is not mapped to db perhaps'
73 ' it was created or renamed from the file system'
74 ' it was created or renamed from the file system'
74 ' please run the application again'
75 ' please run the application again'
75 ' in order to rescan repositories') % repo_name,
76 ' in order to rescan repositories') % repo_name,
76 category='error')
77 category='error')
77
78
78 return redirect(url('home'))
79 return redirect(url('home'))
79
80
80 self.__load_defaults()
81 self.__load_defaults()
81
82
82 defaults = RepoModel()._get_defaults(repo_name)
83 defaults = RepoModel()._get_defaults(repo_name)
83
84
84 return htmlfill.render(
85 return htmlfill.render(
85 render('settings/repo_settings.html'),
86 render('settings/repo_settings.html'),
86 defaults=defaults,
87 defaults=defaults,
87 encoding="UTF-8",
88 encoding="UTF-8",
88 force_defaults=False
89 force_defaults=False
89 )
90 )
90
91
91 @HasRepoPermissionAllDecorator('repository.admin')
92 @HasRepoPermissionAllDecorator('repository.admin')
92 def update(self, repo_name):
93 def update(self, repo_name):
93 repo_model = RepoModel()
94 repo_model = RepoModel()
94 changed_name = repo_name
95 changed_name = repo_name
95
96
96 self.__load_defaults()
97 self.__load_defaults()
97
98
98 _form = RepoSettingsForm(edit=True,
99 _form = RepoSettingsForm(edit=True,
99 old_data={'repo_name': repo_name},
100 old_data={'repo_name': repo_name},
100 repo_groups=c.repo_groups_choices,
101 repo_groups=c.repo_groups_choices,
101 landing_revs=c.landing_revs_choices)()
102 landing_revs=c.landing_revs_choices)()
102 try:
103 try:
103 form_result = _form.to_python(dict(request.POST))
104 form_result = _form.to_python(dict(request.POST))
104
105
105 repo_model.update(repo_name, form_result)
106 repo_model.update(repo_name, form_result)
106 invalidate_cache('get_repo_cached_%s' % repo_name)
107 invalidate_cache('get_repo_cached_%s' % repo_name)
107 h.flash(_('Repository %s updated successfully') % repo_name,
108 h.flash(_('Repository %s updated successfully') % repo_name,
108 category='success')
109 category='success')
109 changed_name = form_result['repo_name_full']
110 changed_name = form_result['repo_name_full']
110 action_logger(self.rhodecode_user, 'user_updated_repo',
111 action_logger(self.rhodecode_user, 'user_updated_repo',
111 changed_name, self.ip_addr, self.sa)
112 changed_name, self.ip_addr, self.sa)
112 Session.commit()
113 Session().commit()
113 except formencode.Invalid, errors:
114 except formencode.Invalid, errors:
114 c.repo_info = repo_model.get_by_repo_name(repo_name)
115 c.repo_info = repo_model.get_by_repo_name(repo_name)
115 c.users_array = repo_model.get_users_js()
116 c.users_array = repo_model.get_users_js()
116 errors.value.update({'user': c.repo_info.user.username})
117 errors.value.update({'user': c.repo_info.user.username})
117 return htmlfill.render(
118 return htmlfill.render(
118 render('settings/repo_settings.html'),
119 render('settings/repo_settings.html'),
119 defaults=errors.value,
120 defaults=errors.value,
120 errors=errors.error_dict or {},
121 errors=errors.error_dict or {},
121 prefix_error=False,
122 prefix_error=False,
122 encoding="UTF-8")
123 encoding="UTF-8")
123 except Exception:
124 except Exception:
124 log.error(traceback.format_exc())
125 log.error(traceback.format_exc())
125 h.flash(_('error occurred during update of repository %s') \
126 h.flash(_('error occurred during update of repository %s') \
126 % repo_name, category='error')
127 % repo_name, category='error')
127
128
128 return redirect(url('repo_settings_home', repo_name=changed_name))
129 return redirect(url('repo_settings_home', repo_name=changed_name))
129
130
130 @HasRepoPermissionAllDecorator('repository.admin')
131 @HasRepoPermissionAllDecorator('repository.admin')
131 def delete(self, repo_name):
132 def delete(self, repo_name):
132 """DELETE /repos/repo_name: Delete an existing item"""
133 """DELETE /repos/repo_name: Delete an existing item"""
133 # Forms posted to this method should contain a hidden field:
134 # Forms posted to this method should contain a hidden field:
134 # <input type="hidden" name="_method" value="DELETE" />
135 # <input type="hidden" name="_method" value="DELETE" />
135 # Or using helpers:
136 # Or using helpers:
136 # h.form(url('repo_settings_delete', repo_name=ID),
137 # h.form(url('repo_settings_delete', repo_name=ID),
137 # method='delete')
138 # method='delete')
138 # url('repo_settings_delete', repo_name=ID)
139 # url('repo_settings_delete', repo_name=ID)
139
140
140 repo_model = RepoModel()
141 repo_model = RepoModel()
141 repo = repo_model.get_by_repo_name(repo_name)
142 repo = repo_model.get_by_repo_name(repo_name)
142 if not repo:
143 if not repo:
143 h.flash(_('%s repository is not mapped to db perhaps'
144 h.flash(_('%s repository is not mapped to db perhaps'
144 ' it was moved or renamed from the filesystem'
145 ' it was moved or renamed from the filesystem'
145 ' please run the application again'
146 ' please run the application again'
146 ' in order to rescan repositories') % repo_name,
147 ' in order to rescan repositories') % repo_name,
147 category='error')
148 category='error')
148
149
149 return redirect(url('home'))
150 return redirect(url('home'))
150 try:
151 try:
151 action_logger(self.rhodecode_user, 'user_deleted_repo',
152 action_logger(self.rhodecode_user, 'user_deleted_repo',
152 repo_name, self.ip_addr, self.sa)
153 repo_name, self.ip_addr, self.sa)
153 repo_model.delete(repo)
154 repo_model.delete(repo)
154 invalidate_cache('get_repo_cached_%s' % repo_name)
155 invalidate_cache('get_repo_cached_%s' % repo_name)
155 h.flash(_('deleted repository %s') % repo_name, category='success')
156 h.flash(_('deleted repository %s') % repo_name, category='success')
156 Session.commit()
157 Session().commit()
157 except Exception:
158 except Exception:
158 log.error(traceback.format_exc())
159 log.error(traceback.format_exc())
159 h.flash(_('An error occurred during deletion of %s') % repo_name,
160 h.flash(_('An error occurred during deletion of %s') % repo_name,
160 category='error')
161 category='error')
161
162
162 return redirect(url('home'))
163 return redirect(url('home'))
164
165 @HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
166 def toggle_locking(self, repo_name):
167 """
168 Toggle locking of repository by simple GET call to url
169
170 :param repo_name:
171 """
172
173 try:
174 repo = Repository.get_by_repo_name(repo_name)
175
176 if repo.enable_locking:
177 if repo.locked[0]:
178 Repository.unlock(repo)
179 action = _('unlocked')
180 else:
181 Repository.lock(repo, c.rhodecode_user.user_id)
182 action = _('locked')
183
184 h.flash(_('Repository has been %s') % action,
185 category='success')
186 except Exception, e:
187 log.error(traceback.format_exc())
188 h.flash(_('An error occurred during unlocking'),
189 category='error')
190 return redirect(url('summary_home', repo_name=repo_name))
191
@@ -1,63 +1,64 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.shortlog
3 rhodecode.controllers.shortlog
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Shortlog controller for rhodecode
6 Shortlog controller 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) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 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
27
28 from pylons import tmpl_context as c, request, url
28 from pylons import tmpl_context as c, request, url
29
29
30 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
30 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
31 from rhodecode.lib.base import BaseRepoController, render
31 from rhodecode.lib.base import BaseRepoController, render
32 from rhodecode.lib.helpers import RepoPage
32 from rhodecode.lib.helpers import RepoPage
33 from pylons.controllers.util import redirect
33 from pylons.controllers.util import redirect
34 from rhodecode.lib.utils2 import safe_int
34
35
35 log = logging.getLogger(__name__)
36 log = logging.getLogger(__name__)
36
37
37
38
38 class ShortlogController(BaseRepoController):
39 class ShortlogController(BaseRepoController):
39
40
40 @LoginRequired()
41 @LoginRequired()
41 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
42 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
42 'repository.admin')
43 'repository.admin')
43 def __before__(self):
44 def __before__(self):
44 super(ShortlogController, self).__before__()
45 super(ShortlogController, self).__before__()
45
46
46 def index(self, repo_name):
47 def index(self, repo_name):
47 p = int(request.params.get('page', 1))
48 p = safe_int(request.params.get('page', 1), 1)
48 size = int(request.params.get('size', 20))
49 size = safe_int(request.params.get('size', 20), 20)
49
50
50 def url_generator(**kw):
51 def url_generator(**kw):
51 return url('shortlog_home', repo_name=repo_name, size=size, **kw)
52 return url('shortlog_home', repo_name=repo_name, size=size, **kw)
52
53
53 c.repo_changesets = RepoPage(c.rhodecode_repo, page=p,
54 c.repo_changesets = RepoPage(c.rhodecode_repo, page=p,
54 items_per_page=size, url=url_generator)
55 items_per_page=size, url=url_generator)
55
56
56 if not c.repo_changesets:
57 if not c.repo_changesets:
57 return redirect(url('summary_home', repo_name=repo_name))
58 return redirect(url('summary_home', repo_name=repo_name))
58
59
59 c.shortlog_data = render('shortlog/shortlog_data.html')
60 c.shortlog_data = render('shortlog/shortlog_data.html')
60 if request.environ.get('HTTP_X_PARTIAL_XHR'):
61 if request.environ.get('HTTP_X_PARTIAL_XHR'):
61 return c.shortlog_data
62 return c.shortlog_data
62 r = render('shortlog/shortlog.html')
63 r = render('shortlog/shortlog.html')
63 return r
64 return r
1 NO CONTENT: modified file, binary diff hidden
NO CONTENT: modified file, binary diff hidden
This diff has been collapsed as it changes many lines, (1286 lines changed) Show them Hide them
@@ -1,4022 +1,3894 b''
1 # Chinese (China) translations for RhodeCode.
1 # Chinese (China) translations for RhodeCode.
2 # Copyright (C) 2011 ORGANIZATION
2 # Copyright (C) 2011 ORGANIZATION
3 # This file is distributed under the same license as the RhodeCode project.
3 # This file is distributed under the same license as the RhodeCode project.
4 # FIRST AUTHOR <EMAIL@ADDRESS>, 2011.
4 # FIRST AUTHOR <EMAIL@ADDRESS>, 2011.
5 # mikespook <mikespook@gmail.com>, 2012.
5 # mikespook <mikespook@gmail.com>, 2012.
6 # xpol <xpolife@gmail.com>, 2012.
6 msgid ""
7 msgid ""
7 msgstr ""
8 msgstr ""
8 "Project-Id-Version: RhodeCode 1.2.0\n"
9 "Project-Id-Version: RhodeCode 1.2.0\n"
9 "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
10 "Report-Msgid-Bugs-To: EMAIL@ADDRESS\n"
10 "POT-Creation-Date: 2012-09-02 20:30+0200\n"
11 "POT-Creation-Date: 2012-09-02 20:30+0200\n"
11 "PO-Revision-Date: 2012-04-05 17:37+0800\n"
12 "PO-Revision-Date: 2012-09-19 13:27+0800\n"
12 "Last-Translator: mikespook <mikespook@gmail.com>\n"
13 "Last-Translator: xpol <xpolife@gmail.com>\n"
13 "Language-Team: mikespook\n"
14 "Language-Team: mikespook\n"
14 "Plural-Forms: nplurals=1; plural=0\n"
15 "Plural-Forms: nplurals=1; plural=0;\n"
15 "MIME-Version: 1.0\n"
16 "MIME-Version: 1.0\n"
16 "Content-Type: text/plain; charset=utf-8\n"
17 "Content-Type: text/plain; charset=UTF-8\n"
17 "Content-Transfer-Encoding: 8bit\n"
18 "Content-Transfer-Encoding: 8bit\n"
18 "Generated-By: Babel 0.9.6\n"
19 "Generated-By: Babel 0.9.6\n"
20 "X-Generator: Poedit 1.5.3\n"
21 "X-Poedit-Basepath: E:\\home\\rhodecode\n"
22 "X-Poedit-SourceCharset: UTF-8\n"
19
23
20 #: rhodecode/controllers/changelog.py:94
24 #: rhodecode/controllers/changelog.py:94
21 #, fuzzy
22 msgid "All Branches"
25 msgid "All Branches"
23 msgstr "分支"
26 msgstr "所有分支"
24
27
25 #: rhodecode/controllers/changeset.py:83
28 #: rhodecode/controllers/changeset.py:83
26 msgid "show white space"
29 msgid "show white space"
27 msgstr ""
30 msgstr "显示空白字符"
28
31
29 #: rhodecode/controllers/changeset.py:90 rhodecode/controllers/changeset.py:97
32 #: rhodecode/controllers/changeset.py:90 rhodecode/controllers/changeset.py:97
30 msgid "ignore white space"
33 msgid "ignore white space"
31 msgstr ""
34 msgstr "忽略空白字符"
32
35
33 #: rhodecode/controllers/changeset.py:157
36 #: rhodecode/controllers/changeset.py:157
34 #, fuzzy, python-format
37 #, python-format
35 msgid "%s line context"
38 msgid "%s line context"
36 msgstr "文件内容"
39 msgstr "%s 行上下文"
37
40
38 #: rhodecode/controllers/changeset.py:333
41 #: rhodecode/controllers/changeset.py:333
39 #: rhodecode/controllers/changeset.py:348 rhodecode/lib/diffs.py:70
42 #: rhodecode/controllers/changeset.py:348 rhodecode/lib/diffs.py:70
40 msgid "binary file"
43 msgid "binary file"
41 msgstr "二进制文件"
44 msgstr "二进制文件"
42
45
43 #: rhodecode/controllers/changeset.py:408
46 #: rhodecode/controllers/changeset.py:408
44 msgid ""
47 msgid ""
45 "Changing status on a changeset associated witha closed pull request is "
48 "Changing status on a changeset associated witha closed pull request is not "
46 "not allowed"
49 "allowed"
47 msgstr ""
50 msgstr "不允许修改已关闭拉取请求的修订集状态"
48
51
49 #: rhodecode/controllers/compare.py:69
52 #: rhodecode/controllers/compare.py:69
50 #, fuzzy
51 msgid "There are no changesets yet"
53 msgid "There are no changesets yet"
52 msgstr "没有任何变更"
54 msgstr "还没有修订集"
53
55
54 #: rhodecode/controllers/error.py:69
56 #: rhodecode/controllers/error.py:69
55 msgid "Home page"
57 msgid "Home page"
56 msgstr "主页"
58 msgstr "主页"
57
59
58 #: rhodecode/controllers/error.py:98
60 #: rhodecode/controllers/error.py:98
59 msgid "The request could not be understood by the server due to malformed syntax."
61 msgid ""
62 "The request could not be understood by the server due to malformed syntax."
60 msgstr "由于错误的语法,服务器无法对请求进行响应。"
63 msgstr "由于错误的语法,服务器无法对请求进行响应。"
61
64
62 #: rhodecode/controllers/error.py:101
65 #: rhodecode/controllers/error.py:101
63 msgid "Unauthorized access to resource"
66 msgid "Unauthorized access to resource"
64 msgstr "未授权的资源访问"
67 msgstr "未授权的资源访问"
65
68
66 #: rhodecode/controllers/error.py:103
69 #: rhodecode/controllers/error.py:103
67 msgid "You don't have permission to view this page"
70 msgid "You don't have permission to view this page"
68 msgstr "无权访问该页面"
71 msgstr "无权访问该页面"
69
72
70 #: rhodecode/controllers/error.py:105
73 #: rhodecode/controllers/error.py:105
71 msgid "The resource could not be found"
74 msgid "The resource could not be found"
72 msgstr "资源未找到"
75 msgstr "资源未找到"
73
76
74 #: rhodecode/controllers/error.py:107
77 #: rhodecode/controllers/error.py:107
75 msgid ""
78 msgid ""
76 "The server encountered an unexpected condition which prevented it from "
79 "The server encountered an unexpected condition which prevented it from "
77 "fulfilling the request."
80 "fulfilling the request."
78 msgstr "服务进入非预期的混乱状态,这会阻止它对请求进行响应。"
81 msgstr "服务进入非预期的混乱状态,这会阻止它对请求进行响应。"
79
82
80 #: rhodecode/controllers/feed.py:49
83 #: rhodecode/controllers/feed.py:49
81 #, python-format
84 #, python-format
82 msgid "Changes on %s repository"
85 msgid "Changes on %s repository"
83 msgstr "%s 库的修改"
86 msgstr "%s 库的修改"
84
87
85 #: rhodecode/controllers/feed.py:50
88 #: rhodecode/controllers/feed.py:50
86 #, python-format
89 #, python-format
87 msgid "%s %s feed"
90 msgid "%s %s feed"
88 msgstr "%s %s 订阅"
91 msgstr "%s %s 订阅"
89
92
90 #: rhodecode/controllers/feed.py:75
93 #: rhodecode/controllers/feed.py:75
91 #, fuzzy
92 msgid "commited on"
94 msgid "commited on"
93 msgstr "提交"
95 msgstr "提交"
94
96
95 #: rhodecode/controllers/files.py:84
97 #: rhodecode/controllers/files.py:84
96 #, fuzzy
97 msgid "click here to add new file"
98 msgid "click here to add new file"
98 msgstr "添加新用户"
99 msgstr "点击此处添加新文件"
99
100
100 #: rhodecode/controllers/files.py:85
101 #: rhodecode/controllers/files.py:85
101 #, fuzzy, python-format
102 #, python-format
102 msgid "There are no files yet %s"
103 msgid "There are no files yet %s"
103 msgstr "尚无文件"
104 msgstr "还没有文件 %s"
104
105
105 #: rhodecode/controllers/files.py:239 rhodecode/controllers/files.py:299
106 #: rhodecode/controllers/files.py:239 rhodecode/controllers/files.py:299
106 #, python-format
107 #, python-format
107 msgid "This repository is has been locked by %s on %s"
108 msgid "This repository is has been locked by %s on %s"
108 msgstr ""
109 msgstr "版本库由 %s 于 %s 锁定"
109
110
110 #: rhodecode/controllers/files.py:266
111 #: rhodecode/controllers/files.py:266
111 #, python-format
112 #, python-format
112 msgid "Edited %s via RhodeCode"
113 msgid "Edited %s via RhodeCode"
113 msgstr "通过 RhodeCode 修改了 %s"
114 msgstr "通过 RhodeCode 修改了 %s"
114
115
115 #: rhodecode/controllers/files.py:271
116 #: rhodecode/controllers/files.py:271
116 msgid "No changes"
117 msgid "No changes"
117 msgstr "无变更"
118 msgstr "无变更"
118
119
119 #: rhodecode/controllers/files.py:282 rhodecode/controllers/files.py:346
120 #: rhodecode/controllers/files.py:282 rhodecode/controllers/files.py:346
120 #, python-format
121 #, python-format
121 msgid "Successfully committed to %s"
122 msgid "Successfully committed to %s"
122 msgstr "成功提交到 %s"
123 msgstr "成功提交到 %s"
123
124
124 #: rhodecode/controllers/files.py:287 rhodecode/controllers/files.py:352
125 #: rhodecode/controllers/files.py:287 rhodecode/controllers/files.py:352
125 msgid "Error occurred during commit"
126 msgid "Error occurred during commit"
126 msgstr "提交时发生错误"
127 msgstr "提交时发生错误"
127
128
128 #: rhodecode/controllers/files.py:318
129 #: rhodecode/controllers/files.py:318
129 #, fuzzy, python-format
130 #, python-format
130 msgid "Added %s via RhodeCode"
131 msgid "Added %s via RhodeCode"
131 msgstr "通过 RhodeCode 修改了 %s"
132 msgstr "通过 RhodeCode 添加了 %s"
132
133
133 #: rhodecode/controllers/files.py:332
134 #: rhodecode/controllers/files.py:332
134 #, fuzzy
135 msgid "No content"
135 msgid "No content"
136 msgstr "文件内容"
136 msgstr "内容"
137
137
138 #: rhodecode/controllers/files.py:336
138 #: rhodecode/controllers/files.py:336
139 #, fuzzy
140 msgid "No filename"
139 msgid "No filename"
141 msgstr "文件名"
140 msgstr "文件名"
142
141
143 #: rhodecode/controllers/files.py:378
142 #: rhodecode/controllers/files.py:378
144 msgid "downloads disabled"
143 msgid "downloads disabled"
145 msgstr "禁止下载"
144 msgstr "禁止下载"
146
145
147 #: rhodecode/controllers/files.py:389
146 #: rhodecode/controllers/files.py:389
148 #, python-format
147 #, python-format
149 msgid "Unknown revision %s"
148 msgid "Unknown revision %s"
150 msgstr "未知版本 %s"
149 msgstr "未知版本 %s"
151
150
152 #: rhodecode/controllers/files.py:391
151 #: rhodecode/controllers/files.py:391
153 msgid "Empty repository"
152 msgid "Empty repository"
154 msgstr "空版本库"
153 msgstr "空版本库"
155
154
156 #: rhodecode/controllers/files.py:393
155 #: rhodecode/controllers/files.py:393
157 msgid "Unknown archive type"
156 msgid "Unknown archive type"
158 msgstr "未知包类型"
157 msgstr "未知包类型"
159
158
160 #: rhodecode/controllers/files.py:494
159 #: rhodecode/controllers/files.py:494
161 #: rhodecode/templates/changeset/changeset_range.html:13
160 #: rhodecode/templates/changeset/changeset_range.html:13
162 #: rhodecode/templates/changeset/changeset_range.html:31
161 #: rhodecode/templates/changeset/changeset_range.html:31
163 msgid "Changesets"
162 msgid "Changesets"
164 msgstr "变更集"
163 msgstr "修订集"
165
164
166 #: rhodecode/controllers/files.py:495 rhodecode/controllers/pullrequests.py:72
165 #: rhodecode/controllers/files.py:495 rhodecode/controllers/pullrequests.py:72
167 #: rhodecode/controllers/summary.py:232 rhodecode/model/scm.py:543
166 #: rhodecode/controllers/summary.py:232 rhodecode/model/scm.py:543
168 msgid "Branches"
167 msgid "Branches"
169 msgstr "分支"
168 msgstr "分支"
170
169
171 #: rhodecode/controllers/files.py:496 rhodecode/controllers/pullrequests.py:76
170 #: rhodecode/controllers/files.py:496 rhodecode/controllers/pullrequests.py:76
172 #: rhodecode/controllers/summary.py:233 rhodecode/model/scm.py:554
171 #: rhodecode/controllers/summary.py:233 rhodecode/model/scm.py:554
173 msgid "Tags"
172 msgid "Tags"
174 msgstr "标签"
173 msgstr "标签"
175
174
176 #: rhodecode/controllers/forks.py:73 rhodecode/controllers/admin/repos.py:90
175 #: rhodecode/controllers/forks.py:73 rhodecode/controllers/admin/repos.py:90
177 #, python-format
176 #, python-format
178 msgid ""
177 msgid ""
179 "%s repository is not mapped to db perhaps it was created or renamed from "
178 "%s repository is not mapped to db perhaps it was created or renamed from the "
180 "the filesystem please run the application again in order to rescan "
179 "filesystem please run the application again in order to rescan repositories"
181 "repositories"
182 msgstr ""
180 msgstr ""
181 "版本库 %s 没有映射到数据库,可能是从文件系统创建或者重命名,请重启 RhodeCode "
182 "以重新扫描版本库"
183
183
184 #: rhodecode/controllers/forks.py:133 rhodecode/controllers/settings.py:72
184 #: rhodecode/controllers/forks.py:133 rhodecode/controllers/settings.py:72
185 #, python-format
185 #, python-format
186 msgid ""
186 msgid ""
187 "%s repository is not mapped to db perhaps it was created or renamed from "
187 "%s repository is not mapped to db perhaps it was created or renamed from the "
188 "the file system please run the application again in order to rescan "
188 "file system please run the application again in order to rescan repositories"
189 "repositories"
190 msgstr ""
189 msgstr ""
190 " 版本库 %s 没有映射到数据库,可能是从文件系统创建或者重命名,请重启 "
191 "RhodeCode 以重新扫描版本库"
191
192
192 #: rhodecode/controllers/forks.py:167
193 #: rhodecode/controllers/forks.py:167
193 #, python-format
194 #, python-format
194 msgid "forked %s repository as %s"
195 msgid "forked %s repository as %s"
195 msgstr "版本库 %s 被分支到 %s"
196 msgstr "版本库 %s 被分支到 %s"
196
197
197 #: rhodecode/controllers/forks.py:181
198 #: rhodecode/controllers/forks.py:181
198 #, python-format
199 #, python-format
199 msgid "An error occurred during repository forking %s"
200 msgid "An error occurred during repository forking %s"
200 msgstr ""
201 msgstr "在分支版本库 %s 的时候发生错误"
201
202
202 #: rhodecode/controllers/journal.py:202 rhodecode/controllers/journal.py:239
203 #: rhodecode/controllers/journal.py:202 rhodecode/controllers/journal.py:239
203 #, fuzzy
204 msgid "public journal"
204 msgid "public journal"
205 msgstr "公共日志"
205 msgstr "公共日志"
206
206
207 #: rhodecode/controllers/journal.py:206 rhodecode/controllers/journal.py:243
207 #: rhodecode/controllers/journal.py:206 rhodecode/controllers/journal.py:243
208 #: rhodecode/templates/base/base.html:220
208 #: rhodecode/templates/base/base.html:220
209 msgid "journal"
209 msgid "journal"
210 msgstr "日志"
210 msgstr "日志"
211
211
212 #: rhodecode/controllers/login.py:143
212 #: rhodecode/controllers/login.py:143
213 msgid "You have successfully registered into rhodecode"
213 msgid "You have successfully registered into rhodecode"
214 msgstr "成功注册到 rhodecode"
214 msgstr "成功注册到 rhodecode"
215
215
216 #: rhodecode/controllers/login.py:164
216 #: rhodecode/controllers/login.py:164
217 msgid "Your password reset link was sent"
217 msgid "Your password reset link was sent"
218 msgstr "密码重置链接已经发送"
218 msgstr "密码重置链接已经发送"
219
219
220 #: rhodecode/controllers/login.py:184
220 #: rhodecode/controllers/login.py:184
221 msgid ""
221 msgid ""
222 "Your password reset was successful, new password has been sent to your "
222 "Your password reset was successful, new password has been sent to your email"
223 "email"
224 msgstr "密码已经成功重置,新密码已经发送到你的邮箱"
223 msgstr "密码已经成功重置,新密码已经发送到你的邮箱"
225
224
226 #: rhodecode/controllers/pullrequests.py:74 rhodecode/model/scm.py:549
225 #: rhodecode/controllers/pullrequests.py:74 rhodecode/model/scm.py:549
227 msgid "Bookmarks"
226 msgid "Bookmarks"
228 msgstr ""
227 msgstr "书签"
229
228
230 #: rhodecode/controllers/pullrequests.py:158
229 #: rhodecode/controllers/pullrequests.py:158
231 msgid "Pull request requires a title with min. 3 chars"
230 msgid "Pull request requires a title with min. 3 chars"
232 msgstr ""
231 msgstr "拉取请求的标题至少 3 个字符"
233
232
234 #: rhodecode/controllers/pullrequests.py:160
233 #: rhodecode/controllers/pullrequests.py:160
235 #, fuzzy
236 msgid "error during creation of pull request"
234 msgid "error during creation of pull request"
237 msgstr "提交时发生错误"
235 msgstr "提交拉取请求时发生错误"
238
236
239 #: rhodecode/controllers/pullrequests.py:181
237 #: rhodecode/controllers/pullrequests.py:181
240 #, fuzzy
241 msgid "Successfully opened new pull request"
238 msgid "Successfully opened new pull request"
242 msgstr "用户删除成功"
239 msgstr "成功提交拉取请求"
243
240
244 #: rhodecode/controllers/pullrequests.py:184
241 #: rhodecode/controllers/pullrequests.py:184
245 #, fuzzy
246 msgid "Error occurred during sending pull request"
242 msgid "Error occurred during sending pull request"
247 msgstr "提交时发生错误"
243 msgstr "提交拉取请求时发生错误"
248
244
249 #: rhodecode/controllers/pullrequests.py:217
245 #: rhodecode/controllers/pullrequests.py:217
250 #, fuzzy
251 msgid "Successfully deleted pull request"
246 msgid "Successfully deleted pull request"
252 msgstr "用户删除成功"
247 msgstr "成功删除拉取请求"
253
248
254 #: rhodecode/controllers/search.py:131
249 #: rhodecode/controllers/search.py:131
255 msgid "Invalid search query. Try quoting it."
250 msgid "Invalid search query. Try quoting it."
256 msgstr "错误的搜索。请尝试用引号包含它。"
251 msgstr "错误的搜索。请尝试用引号包含它。"
257
252
258 #: rhodecode/controllers/search.py:136
253 #: rhodecode/controllers/search.py:136
259 msgid "There is no index to search in. Please run whoosh indexer"
254 msgid "There is no index to search in. Please run whoosh indexer"
260 msgstr "没有索引用于搜索。请运行 whoosh 索引器"
255 msgstr "没有索引用于搜索。请运行 whoosh 索引器"
261
256
262 #: rhodecode/controllers/search.py:140
257 #: rhodecode/controllers/search.py:140
263 msgid "An error occurred during this search operation"
258 msgid "An error occurred during this search operation"
264 msgstr "在搜索操作中发生异常"
259 msgstr "在搜索操作中发生异常"
265
260
266 #: rhodecode/controllers/settings.py:107
261 #: rhodecode/controllers/settings.py:107
267 #: rhodecode/controllers/admin/repos.py:266
262 #: rhodecode/controllers/admin/repos.py:266
268 #, python-format
263 #, python-format
269 msgid "Repository %s updated successfully"
264 msgid "Repository %s updated successfully"
270 msgstr "版本库 %s 成功更新"
265 msgstr "版本库 %s 成功更新"
271
266
272 #: rhodecode/controllers/settings.py:125
267 #: rhodecode/controllers/settings.py:125
273 #: rhodecode/controllers/admin/repos.py:284
268 #: rhodecode/controllers/admin/repos.py:284
274 #, python-format
269 #, python-format
275 msgid "error occurred during update of repository %s"
270 msgid "error occurred during update of repository %s"
276 msgstr ""
271 msgstr "在更新版本库 %s 的时候发生错误"
277
272
278 #: rhodecode/controllers/settings.py:143
273 #: rhodecode/controllers/settings.py:143
279 #: rhodecode/controllers/admin/repos.py:302
274 #: rhodecode/controllers/admin/repos.py:302
280 #, python-format
275 #, python-format
281 msgid ""
276 msgid ""
282 "%s repository is not mapped to db perhaps it was moved or renamed from "
277 "%s repository is not mapped to db perhaps it was moved or renamed from the "
283 "the filesystem please run the application again in order to rescan "
278 "filesystem please run the application again in order to rescan repositories"
284 "repositories"
285 msgstr ""
279 msgstr ""
280 "版本库 %s 没有映射到数据库,可能是从文件系统创建或者重命名,请重启 RhodeCode "
281 "以重新扫描版本库"
286
282
287 #: rhodecode/controllers/settings.py:155
283 #: rhodecode/controllers/settings.py:155
288 #: rhodecode/controllers/admin/repos.py:314
284 #: rhodecode/controllers/admin/repos.py:314
289 #, python-format
285 #, python-format
290 msgid "deleted repository %s"
286 msgid "deleted repository %s"
291 msgstr "已经删除版本库 %s"
287 msgstr "已经删除版本库 %s"
292
288
293 #: rhodecode/controllers/settings.py:159
289 #: rhodecode/controllers/settings.py:159
294 #: rhodecode/controllers/admin/repos.py:324
290 #: rhodecode/controllers/admin/repos.py:324
295 #: rhodecode/controllers/admin/repos.py:330
291 #: rhodecode/controllers/admin/repos.py:330
296 #, python-format
292 #, python-format
297 msgid "An error occurred during deletion of %s"
293 msgid "An error occurred during deletion of %s"
298 msgstr ""
294 msgstr "在删除 %s 的时候发生错误"
299
295
300 #: rhodecode/controllers/summary.py:138
296 #: rhodecode/controllers/summary.py:138
301 msgid "No data loaded yet"
297 msgid "No data loaded yet"
302 msgstr ""
298 msgstr "数据未加载"
303
299
304 #: rhodecode/controllers/summary.py:142
300 #: rhodecode/controllers/summary.py:142
305 #: rhodecode/templates/summary/summary.html:148
301 #: rhodecode/templates/summary/summary.html:148
306 msgid "Statistics are disabled for this repository"
302 msgid "Statistics are disabled for this repository"
307 msgstr "该版本库统计功能已经禁用"
303 msgstr "该版本库统计功能已经禁用"
308
304
309 #: rhodecode/controllers/admin/ldap_settings.py:50
305 #: rhodecode/controllers/admin/ldap_settings.py:50
310 msgid "BASE"
306 msgid "BASE"
311 msgstr ""
307 msgstr "BASE"
312
308
313 #: rhodecode/controllers/admin/ldap_settings.py:51
309 #: rhodecode/controllers/admin/ldap_settings.py:51
314 msgid "ONELEVEL"
310 msgid "ONELEVEL"
315 msgstr ""
311 msgstr "ONELEVEL"
316
312
317 #: rhodecode/controllers/admin/ldap_settings.py:52
313 #: rhodecode/controllers/admin/ldap_settings.py:52
318 msgid "SUBTREE"
314 msgid "SUBTREE"
319 msgstr ""
315 msgstr "SUBTREE"
320
316
321 #: rhodecode/controllers/admin/ldap_settings.py:56
317 #: rhodecode/controllers/admin/ldap_settings.py:56
322 msgid "NEVER"
318 msgid "NEVER"
323 msgstr ""
319 msgstr "NEVER"
324
320
325 #: rhodecode/controllers/admin/ldap_settings.py:57
321 #: rhodecode/controllers/admin/ldap_settings.py:57
326 msgid "ALLOW"
322 msgid "ALLOW"
327 msgstr ""
323 msgstr "ALLOW"
328
324
329 #: rhodecode/controllers/admin/ldap_settings.py:58
325 #: rhodecode/controllers/admin/ldap_settings.py:58
330 msgid "TRY"
326 msgid "TRY"
331 msgstr ""
327 msgstr "TRY"
332
328
333 #: rhodecode/controllers/admin/ldap_settings.py:59
329 #: rhodecode/controllers/admin/ldap_settings.py:59
334 msgid "DEMAND"
330 msgid "DEMAND"
335 msgstr ""
331 msgstr "DEMAND"
336
332
337 #: rhodecode/controllers/admin/ldap_settings.py:60
333 #: rhodecode/controllers/admin/ldap_settings.py:60
338 msgid "HARD"
334 msgid "HARD"
339 msgstr ""
335 msgstr "HARD"
340
336
341 #: rhodecode/controllers/admin/ldap_settings.py:64
337 #: rhodecode/controllers/admin/ldap_settings.py:64
342 msgid "No encryption"
338 msgid "No encryption"
343 msgstr "未加密"
339 msgstr "未加密"
344
340
345 #: rhodecode/controllers/admin/ldap_settings.py:65
341 #: rhodecode/controllers/admin/ldap_settings.py:65
346 msgid "LDAPS connection"
342 msgid "LDAPS connection"
347 msgstr ""
343 msgstr "LDAPS 连接"
348
344
349 #: rhodecode/controllers/admin/ldap_settings.py:66
345 #: rhodecode/controllers/admin/ldap_settings.py:66
350 msgid "START_TLS on LDAP connection"
346 msgid "START_TLS on LDAP connection"
351 msgstr ""
347 msgstr "LDAP 连接上的 START_TLS"
352
348
353 #: rhodecode/controllers/admin/ldap_settings.py:126
349 #: rhodecode/controllers/admin/ldap_settings.py:126
354 msgid "Ldap settings updated successfully"
350 msgid "Ldap settings updated successfully"
355 msgstr "LDAP 设置已经成功更新"
351 msgstr "LDAP 设置已经成功更新"
356
352
357 #: rhodecode/controllers/admin/ldap_settings.py:130
353 #: rhodecode/controllers/admin/ldap_settings.py:130
358 msgid "Unable to activate ldap. The \"python-ldap\" library is missing."
354 msgid "Unable to activate ldap. The \"python-ldap\" library is missing."
359 msgstr "无法启用 LDAP。“python-ldap”缺失。"
355 msgstr "无法启用 LDAP。缺失“python-ldap”。"
360
356
361 #: rhodecode/controllers/admin/ldap_settings.py:147
357 #: rhodecode/controllers/admin/ldap_settings.py:147
362 msgid "error occurred during update of ldap settings"
358 msgid "error occurred during update of ldap settings"
363 msgstr ""
359 msgstr "更新 LDAP 设置时发生错误"
364
360
365 #: rhodecode/controllers/admin/permissions.py:59
361 #: rhodecode/controllers/admin/permissions.py:59
366 msgid "None"
362 msgid "None"
367 msgstr "无"
363 msgstr "无"
368
364
369 #: rhodecode/controllers/admin/permissions.py:60
365 #: rhodecode/controllers/admin/permissions.py:60
370 msgid "Read"
366 msgid "Read"
371 msgstr "读"
367 msgstr "读"
372
368
373 #: rhodecode/controllers/admin/permissions.py:61
369 #: rhodecode/controllers/admin/permissions.py:61
374 msgid "Write"
370 msgid "Write"
375 msgstr "写"
371 msgstr "写"
376
372
377 #: rhodecode/controllers/admin/permissions.py:62
373 #: rhodecode/controllers/admin/permissions.py:62
378 #: rhodecode/templates/admin/ldap/ldap.html:9
374 #: rhodecode/templates/admin/ldap/ldap.html:9
379 #: rhodecode/templates/admin/permissions/permissions.html:9
375 #: rhodecode/templates/admin/permissions/permissions.html:9
380 #: rhodecode/templates/admin/repos/repo_add.html:9
376 #: rhodecode/templates/admin/repos/repo_add.html:9
381 #: rhodecode/templates/admin/repos/repo_edit.html:9
377 #: rhodecode/templates/admin/repos/repo_edit.html:9
382 #: rhodecode/templates/admin/repos/repos.html:9
378 #: rhodecode/templates/admin/repos/repos.html:9
383 #: rhodecode/templates/admin/repos_groups/repos_groups_add.html:8
379 #: rhodecode/templates/admin/repos_groups/repos_groups_add.html:8
384 #: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:8
380 #: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:8
385 #: rhodecode/templates/admin/repos_groups/repos_groups_show.html:10
381 #: rhodecode/templates/admin/repos_groups/repos_groups_show.html:10
386 #: rhodecode/templates/admin/settings/hooks.html:9
382 #: rhodecode/templates/admin/settings/hooks.html:9
387 #: rhodecode/templates/admin/settings/settings.html:9
383 #: rhodecode/templates/admin/settings/settings.html:9
388 #: rhodecode/templates/admin/users/user_add.html:8
384 #: rhodecode/templates/admin/users/user_add.html:8
389 #: rhodecode/templates/admin/users/user_edit.html:9
385 #: rhodecode/templates/admin/users/user_edit.html:9
390 #: rhodecode/templates/admin/users/user_edit.html:122
386 #: rhodecode/templates/admin/users/user_edit.html:122
391 #: rhodecode/templates/admin/users/users.html:9
387 #: rhodecode/templates/admin/users/users.html:9
392 #: rhodecode/templates/admin/users_groups/users_group_add.html:8
388 #: rhodecode/templates/admin/users_groups/users_group_add.html:8
393 #: rhodecode/templates/admin/users_groups/users_group_edit.html:9
389 #: rhodecode/templates/admin/users_groups/users_group_edit.html:9
394 #: rhodecode/templates/admin/users_groups/users_groups.html:9
390 #: rhodecode/templates/admin/users_groups/users_groups.html:9
395 #: rhodecode/templates/base/base.html:197
391 #: rhodecode/templates/base/base.html:197
396 #: rhodecode/templates/base/base.html:337
392 #: rhodecode/templates/base/base.html:337
397 #: rhodecode/templates/base/base.html:339
393 #: rhodecode/templates/base/base.html:339
398 #: rhodecode/templates/base/base.html:341
394 #: rhodecode/templates/base/base.html:341
399 msgid "Admin"
395 msgid "Admin"
400 msgstr "管理"
396 msgstr "管理"
401
397
402 #: rhodecode/controllers/admin/permissions.py:65
398 #: rhodecode/controllers/admin/permissions.py:65
403 msgid "disabled"
399 msgid "disabled"
404 msgstr "禁用"
400 msgstr "禁用"
405
401
406 #: rhodecode/controllers/admin/permissions.py:67
402 #: rhodecode/controllers/admin/permissions.py:67
407 msgid "allowed with manual account activation"
403 msgid "allowed with manual account activation"
408 msgstr "允许手工启用帐号"
404 msgstr "允许手工启用帐号"
409
405
410 #: rhodecode/controllers/admin/permissions.py:69
406 #: rhodecode/controllers/admin/permissions.py:69
411 msgid "allowed with automatic account activation"
407 msgid "allowed with automatic account activation"
412 msgstr "允许自动启用帐号"
408 msgstr "允许自动启用帐号"
413
409
414 #: rhodecode/controllers/admin/permissions.py:71
410 #: rhodecode/controllers/admin/permissions.py:71
415 #: rhodecode/controllers/admin/permissions.py:74
411 #: rhodecode/controllers/admin/permissions.py:74
416 msgid "Disabled"
412 msgid "Disabled"
417 msgstr "停用"
413 msgstr "停用"
418
414
419 #: rhodecode/controllers/admin/permissions.py:72
415 #: rhodecode/controllers/admin/permissions.py:72
420 #: rhodecode/controllers/admin/permissions.py:75
416 #: rhodecode/controllers/admin/permissions.py:75
421 msgid "Enabled"
417 msgid "Enabled"
422 msgstr "启用"
418 msgstr "启用"
423
419
424 #: rhodecode/controllers/admin/permissions.py:116
420 #: rhodecode/controllers/admin/permissions.py:116
425 msgid "Default permissions updated successfully"
421 msgid "Default permissions updated successfully"
426 msgstr "成功更新默认权限"
422 msgstr "成功更新默认权限"
427
423
428 #: rhodecode/controllers/admin/permissions.py:130
424 #: rhodecode/controllers/admin/permissions.py:130
429 msgid "error occurred during update of permissions"
425 msgid "error occurred during update of permissions"
430 msgstr ""
426 msgstr "更新权限时发生错误"
431
427
432 #: rhodecode/controllers/admin/repos.py:123
428 #: rhodecode/controllers/admin/repos.py:123
433 msgid "--REMOVE FORK--"
429 msgid "--REMOVE FORK--"
434 msgstr ""
430 msgstr "-- 移除分支 --"
435
431
436 #: rhodecode/controllers/admin/repos.py:192
432 #: rhodecode/controllers/admin/repos.py:192
437 #, python-format
433 #, python-format
438 msgid "created repository %s from %s"
434 msgid "created repository %s from %s"
439 msgstr "新版本库 %s 基于 %s 建立。"
435 msgstr "新版本库 %s 基于 %s 建立。"
440
436
441 #: rhodecode/controllers/admin/repos.py:196
437 #: rhodecode/controllers/admin/repos.py:196
442 #, python-format
438 #, python-format
443 msgid "created repository %s"
439 msgid "created repository %s"
444 msgstr "建立版本库 %s"
440 msgstr "建立版本库 %s"
445
441
446 #: rhodecode/controllers/admin/repos.py:227
442 #: rhodecode/controllers/admin/repos.py:227
447 #, python-format
443 #, python-format
448 msgid "error occurred during creation of repository %s"
444 msgid "error occurred during creation of repository %s"
449 msgstr ""
445 msgstr "创建版本库时发生错误 %s"
450
446
451 #: rhodecode/controllers/admin/repos.py:319
447 #: rhodecode/controllers/admin/repos.py:319
452 #, python-format
448 #, python-format
453 msgid "Cannot delete %s it still contains attached forks"
449 msgid "Cannot delete %s it still contains attached forks"
454 msgstr ""
450 msgstr "无法删除 %s 因为它还有其他分支版本库"
455
451
456 #: rhodecode/controllers/admin/repos.py:348
452 #: rhodecode/controllers/admin/repos.py:348
457 msgid "An error occurred during deletion of repository user"
453 msgid "An error occurred during deletion of repository user"
458 msgstr ""
454 msgstr "删除版本库用户时发生错误"
459
455
460 #: rhodecode/controllers/admin/repos.py:367
456 #: rhodecode/controllers/admin/repos.py:367
461 msgid "An error occurred during deletion of repository users groups"
457 msgid "An error occurred during deletion of repository users groups"
462 msgstr ""
458 msgstr "删除版本库用户组时发生错误"
463
459
464 #: rhodecode/controllers/admin/repos.py:385
460 #: rhodecode/controllers/admin/repos.py:385
465 msgid "An error occurred during deletion of repository stats"
461 msgid "An error occurred during deletion of repository stats"
466 msgstr ""
462 msgstr "删除版本库统计时发生错误"
467
463
468 #: rhodecode/controllers/admin/repos.py:402
464 #: rhodecode/controllers/admin/repos.py:402
469 msgid "An error occurred during cache invalidation"
465 msgid "An error occurred during cache invalidation"
470 msgstr ""
466 msgstr "清除缓存时发生错误"
471
467
472 #: rhodecode/controllers/admin/repos.py:422
468 #: rhodecode/controllers/admin/repos.py:422
473 #, fuzzy
474 msgid "An error occurred during unlocking"
469 msgid "An error occurred during unlocking"
475 msgstr "在搜索操作中发生异常"
470 msgstr "解锁时发生错误"
476
471
477 #: rhodecode/controllers/admin/repos.py:442
472 #: rhodecode/controllers/admin/repos.py:442
478 msgid "Updated repository visibility in public journal"
473 msgid "Updated repository visibility in public journal"
479 msgstr ""
474 msgstr "成功更新在公共日志中的可见性"
480
475
481 #: rhodecode/controllers/admin/repos.py:446
476 #: rhodecode/controllers/admin/repos.py:446
482 msgid "An error occurred during setting this repository in public journal"
477 msgid "An error occurred during setting this repository in public journal"
483 msgstr ""
478 msgstr "设置版本库到公共日志时发生错误"
484
479
485 #: rhodecode/controllers/admin/repos.py:451 rhodecode/model/validators.py:299
480 #: rhodecode/controllers/admin/repos.py:451 rhodecode/model/validators.py:299
486 msgid "Token mismatch"
481 msgid "Token mismatch"
487 msgstr ""
482 msgstr "令牌不匹配"
488
483
489 #: rhodecode/controllers/admin/repos.py:464
484 #: rhodecode/controllers/admin/repos.py:464
490 msgid "Pulled from remote location"
485 msgid "Pulled from remote location"
491 msgstr ""
486 msgstr "成功拉取自远程路径"
492
487
493 #: rhodecode/controllers/admin/repos.py:466
488 #: rhodecode/controllers/admin/repos.py:466
494 msgid "An error occurred during pull from remote location"
489 msgid "An error occurred during pull from remote location"
495 msgstr ""
490 msgstr "从远程路径拉取时发生错误"
496
491
497 #: rhodecode/controllers/admin/repos.py:482
492 #: rhodecode/controllers/admin/repos.py:482
498 msgid "Nothing"
493 msgid "Nothing"
499 msgstr ""
494 msgstr ""
500
495
501 #: rhodecode/controllers/admin/repos.py:484
496 #: rhodecode/controllers/admin/repos.py:484
502 #, fuzzy, python-format
497 #, python-format
503 msgid "Marked repo %s as fork of %s"
498 msgid "Marked repo %s as fork of %s"
504 msgstr "新版本库 %s 基于 %s 建立。"
499 msgstr "成功将版本库 %s 标记为从 %s 分支"
505
500
506 #: rhodecode/controllers/admin/repos.py:488
501 #: rhodecode/controllers/admin/repos.py:488
507 #, fuzzy
508 msgid "An error occurred during this operation"
502 msgid "An error occurred during this operation"
509 msgstr "在搜索操作中发生异常"
503 msgstr "在搜索操作中发生错误"
510
504
511 #: rhodecode/controllers/admin/repos_groups.py:116
505 #: rhodecode/controllers/admin/repos_groups.py:116
512 #, python-format
506 #, python-format
513 msgid "created repos group %s"
507 msgid "created repos group %s"
514 msgstr "建立版本库组 %s"
508 msgstr "建立版本库组 %s"
515
509
516 #: rhodecode/controllers/admin/repos_groups.py:129
510 #: rhodecode/controllers/admin/repos_groups.py:129
517 #, python-format
511 #, python-format
518 msgid "error occurred during creation of repos group %s"
512 msgid "error occurred during creation of repos group %s"
519 msgstr ""
513 msgstr "创建版本库组时发生错误 %s"
520
514
521 #: rhodecode/controllers/admin/repos_groups.py:163
515 #: rhodecode/controllers/admin/repos_groups.py:163
522 #, python-format
516 #, python-format
523 msgid "updated repos group %s"
517 msgid "updated repos group %s"
524 msgstr "更新版本库组 %s"
518 msgstr "更新版本库组 %s"
525
519
526 #: rhodecode/controllers/admin/repos_groups.py:176
520 #: rhodecode/controllers/admin/repos_groups.py:176
527 #, python-format
521 #, python-format
528 msgid "error occurred during update of repos group %s"
522 msgid "error occurred during update of repos group %s"
529 msgstr ""
523 msgstr "更新版本库组时发生错误 %s"
530
524
531 #: rhodecode/controllers/admin/repos_groups.py:194
525 #: rhodecode/controllers/admin/repos_groups.py:194
532 #, python-format
526 #, python-format
533 msgid "This group contains %s repositores and cannot be deleted"
527 msgid "This group contains %s repositores and cannot be deleted"
534 msgstr ""
528 msgstr "这个组内有 %s 个版本库因而无法删除"
535
529
536 #: rhodecode/controllers/admin/repos_groups.py:202
530 #: rhodecode/controllers/admin/repos_groups.py:202
537 #, python-format
531 #, python-format
538 msgid "removed repos group %s"
532 msgid "removed repos group %s"
539 msgstr "移除版本库组 %s"
533 msgstr "移除版本库组 %s"
540
534
541 #: rhodecode/controllers/admin/repos_groups.py:208
535 #: rhodecode/controllers/admin/repos_groups.py:208
542 msgid "Cannot delete this group it still contains subgroups"
536 msgid "Cannot delete this group it still contains subgroups"
543 msgstr ""
537 msgstr "不能删除包含子组的组"
544
538
545 #: rhodecode/controllers/admin/repos_groups.py:213
539 #: rhodecode/controllers/admin/repos_groups.py:213
546 #: rhodecode/controllers/admin/repos_groups.py:218
540 #: rhodecode/controllers/admin/repos_groups.py:218
547 #, python-format
541 #, python-format
548 msgid "error occurred during deletion of repos group %s"
542 msgid "error occurred during deletion of repos group %s"
549 msgstr ""
543 msgstr "删除版本库组时发生错误 %s"
550
544
551 #: rhodecode/controllers/admin/repos_groups.py:238
545 #: rhodecode/controllers/admin/repos_groups.py:238
552 #, fuzzy
553 msgid "An error occurred during deletion of group user"
546 msgid "An error occurred during deletion of group user"
554 msgstr "在搜索操作中发生异常"
547 msgstr "删除组用户时发生错误"
555
548
556 #: rhodecode/controllers/admin/repos_groups.py:258
549 #: rhodecode/controllers/admin/repos_groups.py:258
557 msgid "An error occurred during deletion of group users groups"
550 msgid "An error occurred during deletion of group users groups"
558 msgstr ""
551 msgstr "删除版本库组的用户组时发生错误"
559
552
560 #: rhodecode/controllers/admin/settings.py:121
553 #: rhodecode/controllers/admin/settings.py:121
561 #, python-format
554 #, python-format
562 msgid "Repositories successfully rescanned added: %s,removed: %s"
555 msgid "Repositories successfully rescanned added: %s,removed: %s"
563 msgstr ""
556 msgstr "重新扫描版本库成功,增加 %s, 移除 %s"
564
557
565 #: rhodecode/controllers/admin/settings.py:129
558 #: rhodecode/controllers/admin/settings.py:129
566 msgid "Whoosh reindex task scheduled"
559 msgid "Whoosh reindex task scheduled"
567 msgstr "Whoosh 重新索引任务调度"
560 msgstr "Whoosh 重新索引任务调度"
568
561
569 #: rhodecode/controllers/admin/settings.py:160
562 #: rhodecode/controllers/admin/settings.py:160
570 msgid "Updated application settings"
563 msgid "Updated application settings"
571 msgstr "更新应用设置"
564 msgstr "更新应用设置"
572
565
573 #: rhodecode/controllers/admin/settings.py:164
566 #: rhodecode/controllers/admin/settings.py:164
574 #: rhodecode/controllers/admin/settings.py:275
567 #: rhodecode/controllers/admin/settings.py:275
575 msgid "error occurred during updating application settings"
568 msgid "error occurred during updating application settings"
576 msgstr ""
569 msgstr "更新设置时发生错误"
577
570
578 #: rhodecode/controllers/admin/settings.py:200
571 #: rhodecode/controllers/admin/settings.py:200
579 #, fuzzy
580 msgid "Updated visualisation settings"
572 msgid "Updated visualisation settings"
581 msgstr "更新应用设置"
573 msgstr "成功更新可视化设置"
582
574
583 #: rhodecode/controllers/admin/settings.py:205
575 #: rhodecode/controllers/admin/settings.py:205
584 #, fuzzy
585 msgid "error occurred during updating visualisation settings"
576 msgid "error occurred during updating visualisation settings"
586 msgstr "提交时发生错误"
577 msgstr "更新可视化设置时发生错误"
587
578
588 #: rhodecode/controllers/admin/settings.py:271
579 #: rhodecode/controllers/admin/settings.py:271
589 #, fuzzy
590 msgid "Updated VCS settings"
580 msgid "Updated VCS settings"
591 msgstr "更新 mercurial 设置"
581 msgstr "成功更新版本控制系统设置"
592
582
593 #: rhodecode/controllers/admin/settings.py:285
583 #: rhodecode/controllers/admin/settings.py:285
594 msgid "Added new hook"
584 msgid "Added new hook"
595 msgstr "新钩子"
585 msgstr "新钩子"
596
586
597 #: rhodecode/controllers/admin/settings.py:297
587 #: rhodecode/controllers/admin/settings.py:297
598 msgid "Updated hooks"
588 msgid "Updated hooks"
599 msgstr "更新钩子"
589 msgstr "更新钩子"
600
590
601 #: rhodecode/controllers/admin/settings.py:301
591 #: rhodecode/controllers/admin/settings.py:301
602 msgid "error occurred during hook creation"
592 msgid "error occurred during hook creation"
603 msgstr ""
593 msgstr "创建钩子时发生错误"
604
594
605 #: rhodecode/controllers/admin/settings.py:320
595 #: rhodecode/controllers/admin/settings.py:320
606 msgid "Email task created"
596 msgid "Email task created"
607 msgstr ""
597 msgstr "已创建电子邮件任务"
608
598
609 #: rhodecode/controllers/admin/settings.py:375
599 #: rhodecode/controllers/admin/settings.py:375
610 msgid "You can't edit this user since it's crucial for entire application"
600 msgid "You can't edit this user since it's crucial for entire application"
611 msgstr ""
601 msgstr "由于是系统帐号,无法编辑该用户"
612
602
613 #: rhodecode/controllers/admin/settings.py:406
603 #: rhodecode/controllers/admin/settings.py:406
614 msgid "Your account was updated successfully"
604 msgid "Your account was updated successfully"
615 msgstr "你的帐号已经更新完成"
605 msgstr "你的帐号已经更新完成"
616
606
617 #: rhodecode/controllers/admin/settings.py:421
607 #: rhodecode/controllers/admin/settings.py:421
618 #: rhodecode/controllers/admin/users.py:191
608 #: rhodecode/controllers/admin/users.py:191
619 #, python-format
609 #, python-format
620 msgid "error occurred during update of user %s"
610 msgid "error occurred during update of user %s"
621 msgstr ""
611 msgstr "更新用户 %s 时发生错误"
622
612
623 #: rhodecode/controllers/admin/users.py:130
613 #: rhodecode/controllers/admin/users.py:130
624 #, python-format
614 #, python-format
625 msgid "created user %s"
615 msgid "created user %s"
626 msgstr "创建用户 %s"
616 msgstr "创建用户 %s"
627
617
628 #: rhodecode/controllers/admin/users.py:142
618 #: rhodecode/controllers/admin/users.py:142
629 #, python-format
619 #, python-format
630 msgid "error occurred during creation of user %s"
620 msgid "error occurred during creation of user %s"
631 msgstr ""
621 msgstr "创建用户 %s 时发生错误"
632
622
633 #: rhodecode/controllers/admin/users.py:171
623 #: rhodecode/controllers/admin/users.py:171
634 msgid "User updated successfully"
624 msgid "User updated successfully"
635 msgstr "用户更新成功"
625 msgstr "用户更新成功"
636
626
637 #: rhodecode/controllers/admin/users.py:207
627 #: rhodecode/controllers/admin/users.py:207
638 msgid "successfully deleted user"
628 msgid "successfully deleted user"
639 msgstr "用户删除成功"
629 msgstr "用户删除成功"
640
630
641 #: rhodecode/controllers/admin/users.py:212
631 #: rhodecode/controllers/admin/users.py:212
642 msgid "An error occurred during deletion of user"
632 msgid "An error occurred during deletion of user"
643 msgstr ""
633 msgstr "删除用户时发生错误"
644
634
645 #: rhodecode/controllers/admin/users.py:226
635 #: rhodecode/controllers/admin/users.py:226
646 msgid "You can't edit this user"
636 msgid "You can't edit this user"
647 msgstr "无法编辑该用户"
637 msgstr "无法编辑该用户"
648
638
649 #: rhodecode/controllers/admin/users.py:266
639 #: rhodecode/controllers/admin/users.py:266
650 msgid "Granted 'repository create' permission to user"
640 msgid "Granted 'repository create' permission to user"
651 msgstr ""
641 msgstr "已授予用户‘创建版本库’的权限"
652
642
653 #: rhodecode/controllers/admin/users.py:271
643 #: rhodecode/controllers/admin/users.py:271
654 msgid "Revoked 'repository create' permission to user"
644 msgid "Revoked 'repository create' permission to user"
655 msgstr ""
645 msgstr "已撤销用户‘创建版本库’的权限"
656
646
657 #: rhodecode/controllers/admin/users.py:277
647 #: rhodecode/controllers/admin/users.py:277
658 #, fuzzy
659 msgid "Granted 'repository fork' permission to user"
648 msgid "Granted 'repository fork' permission to user"
660 msgstr "版本库权限"
649 msgstr "成功授予了用户“分支版本库权限"
661
650
662 #: rhodecode/controllers/admin/users.py:282
651 #: rhodecode/controllers/admin/users.py:282
663 #, fuzzy
664 msgid "Revoked 'repository fork' permission to user"
652 msgid "Revoked 'repository fork' permission to user"
665 msgstr "版本库权限"
653 msgstr "成功撤销用户“分支版本库权限"
666
654
667 #: rhodecode/controllers/admin/users.py:288
655 #: rhodecode/controllers/admin/users.py:288
668 #: rhodecode/controllers/admin/users_groups.py:255
656 #: rhodecode/controllers/admin/users_groups.py:255
669 #, fuzzy
670 msgid "An error occurred during permissions saving"
657 msgid "An error occurred during permissions saving"
671 msgstr "在搜索操作中发生异常"
658 msgstr "保存权限时发生错误"
672
659
673 #: rhodecode/controllers/admin/users.py:303
660 #: rhodecode/controllers/admin/users.py:303
674 #, python-format
661 #, python-format
675 msgid "Added email %s to user"
662 msgid "Added email %s to user"
676 msgstr ""
663 msgstr "已为用户添加电子邮件 %s"
677
664
678 #: rhodecode/controllers/admin/users.py:309
665 #: rhodecode/controllers/admin/users.py:309
679 #, fuzzy
680 msgid "An error occurred during email saving"
666 msgid "An error occurred during email saving"
681 msgstr "在搜索操作中发生异常"
667 msgstr "保存电子邮件时发生错误"
682
668
683 #: rhodecode/controllers/admin/users.py:319
669 #: rhodecode/controllers/admin/users.py:319
684 #, fuzzy
685 msgid "Removed email from user"
670 msgid "Removed email from user"
686 msgstr "移除版本库组 %s"
671 msgstr "成功删除用户电子邮件"
687
672
688 #: rhodecode/controllers/admin/users_groups.py:84
673 #: rhodecode/controllers/admin/users_groups.py:84
689 #, python-format
674 #, python-format
690 msgid "created users group %s"
675 msgid "created users group %s"
691 msgstr "建立用户组 %s"
676 msgstr "建立用户组 %s"
692
677
693 #: rhodecode/controllers/admin/users_groups.py:95
678 #: rhodecode/controllers/admin/users_groups.py:95
694 #, python-format
679 #, python-format
695 msgid "error occurred during creation of users group %s"
680 msgid "error occurred during creation of users group %s"
696 msgstr ""
681 msgstr "创建用户组 %s 时发生错误"
697
682
698 #: rhodecode/controllers/admin/users_groups.py:135
683 #: rhodecode/controllers/admin/users_groups.py:135
699 #, python-format
684 #, python-format
700 msgid "updated users group %s"
685 msgid "updated users group %s"
701 msgstr "更新用户组 %s"
686 msgstr "更新用户组 %s"
702
687
703 #: rhodecode/controllers/admin/users_groups.py:157
688 #: rhodecode/controllers/admin/users_groups.py:157
704 #, python-format
689 #, python-format
705 msgid "error occurred during update of users group %s"
690 msgid "error occurred during update of users group %s"
706 msgstr ""
691 msgstr "更新用户组 %s 时发生错误"
707
692
708 #: rhodecode/controllers/admin/users_groups.py:174
693 #: rhodecode/controllers/admin/users_groups.py:174
709 msgid "successfully deleted users group"
694 msgid "successfully deleted users group"
710 msgstr "删除用户组成功"
695 msgstr "删除用户组成功"
711
696
712 #: rhodecode/controllers/admin/users_groups.py:179
697 #: rhodecode/controllers/admin/users_groups.py:179
713 msgid "An error occurred during deletion of users group"
698 msgid "An error occurred during deletion of users group"
714 msgstr ""
699 msgstr "删除用户组时发生错误"
715
700
716 #: rhodecode/controllers/admin/users_groups.py:233
701 #: rhodecode/controllers/admin/users_groups.py:233
717 msgid "Granted 'repository create' permission to users group"
702 msgid "Granted 'repository create' permission to users group"
718 msgstr ""
703 msgstr "已授予用户组‘创建版本库’的权限"
719
704
720 #: rhodecode/controllers/admin/users_groups.py:238
705 #: rhodecode/controllers/admin/users_groups.py:238
721 msgid "Revoked 'repository create' permission to users group"
706 msgid "Revoked 'repository create' permission to users group"
722 msgstr ""
707 msgstr "已撤销用户组‘创建版本库’的权限"
723
708
724 #: rhodecode/controllers/admin/users_groups.py:244
709 #: rhodecode/controllers/admin/users_groups.py:244
725 msgid "Granted 'repository fork' permission to users group"
710 msgid "Granted 'repository fork' permission to users group"
726 msgstr ""
711 msgstr "已授予用户组‘分支版本库’的权限"
727
712
728 #: rhodecode/controllers/admin/users_groups.py:249
713 #: rhodecode/controllers/admin/users_groups.py:249
729 msgid "Revoked 'repository fork' permission to users group"
714 msgid "Revoked 'repository fork' permission to users group"
730 msgstr ""
715 msgstr "已撤销用户组‘分支版本库’的权限"
731
716
732 #: rhodecode/lib/auth.py:499
717 #: rhodecode/lib/auth.py:499
733 msgid "You need to be a registered user to perform this action"
718 msgid "You need to be a registered user to perform this action"
734 msgstr "必须是注册用户才能进行此操作"
719 msgstr "必须是注册用户才能进行此操作"
735
720
736 #: rhodecode/lib/auth.py:540
721 #: rhodecode/lib/auth.py:540
737 msgid "You need to be a signed in to view this page"
722 msgid "You need to be a signed in to view this page"
738 msgstr "必须登录才能访问该页面"
723 msgstr "必须登录才能访问该页面"
739
724
740 #: rhodecode/lib/diffs.py:86
725 #: rhodecode/lib/diffs.py:86
741 msgid "Changeset was too big and was cut off, use diff menu to display this diff"
726 msgid ""
742 msgstr "变更集因过大而被截断,可查看原始变更集作为替代"
727 "Changeset was too big and was cut off, use diff menu to display this diff"
728 msgstr "修订集因过大而被截断,可查看原始修订集作为替代"
743
729
744 #: rhodecode/lib/diffs.py:96
730 #: rhodecode/lib/diffs.py:96
745 #, fuzzy
746 msgid "No changes detected"
731 msgid "No changes detected"
747 msgstr "尚无修订"
732 msgstr "未发现差异"
748
733
749 #: rhodecode/lib/helpers.py:372
734 #: rhodecode/lib/helpers.py:372
750 #, python-format
735 #, python-format
751 msgid "%a, %d %b %Y %H:%M:%S"
736 msgid "%a, %d %b %Y %H:%M:%S"
752 msgstr ""
737 msgstr "%Y/%b/%d %H:%M:%S %a"
753
738
754 #: rhodecode/lib/helpers.py:484
739 #: rhodecode/lib/helpers.py:484
755 msgid "True"
740 msgid "True"
756 msgstr ""
741 msgstr ""
757
742
758 #: rhodecode/lib/helpers.py:488
743 #: rhodecode/lib/helpers.py:488
759 msgid "False"
744 msgid "False"
760 msgstr ""
745 msgstr ""
761
746
762 #: rhodecode/lib/helpers.py:532
747 #: rhodecode/lib/helpers.py:532
763 #, fuzzy
764 msgid "Changeset not found"
748 msgid "Changeset not found"
765 msgstr "修改"
749 msgstr "未找到修订集"
766
750
767 #: rhodecode/lib/helpers.py:555
751 #: rhodecode/lib/helpers.py:555
768 #, python-format
752 #, python-format
769 msgid "Show all combined changesets %s->%s"
753 msgid "Show all combined changesets %s->%s"
770 msgstr ""
754 msgstr "显示合并的修订集 %s->%s"
771
755
772 #: rhodecode/lib/helpers.py:561
756 #: rhodecode/lib/helpers.py:561
773 msgid "compare view"
757 msgid "compare view"
774 msgstr ""
758 msgstr "比较显示"
775
759
776 #: rhodecode/lib/helpers.py:581
760 #: rhodecode/lib/helpers.py:581
777 msgid "and"
761 msgid "and"
778 msgstr ""
762 msgstr "还有"
779
763
780 #: rhodecode/lib/helpers.py:582
764 #: rhodecode/lib/helpers.py:582
781 #, python-format
765 #, python-format
782 msgid "%s more"
766 msgid "%s more"
783 msgstr ""
767 msgstr "%s 个"
784
768
785 #: rhodecode/lib/helpers.py:583 rhodecode/templates/changelog/changelog.html:48
769 #: rhodecode/lib/helpers.py:583
770 #: rhodecode/templates/changelog/changelog.html:48
786 msgid "revisions"
771 msgid "revisions"
787 msgstr "修订"
772 msgstr "修订"
788
773
789 #: rhodecode/lib/helpers.py:606
774 #: rhodecode/lib/helpers.py:606
790 msgid "fork name "
775 msgid "fork name "
791 msgstr "分支名称"
776 msgstr "分支名称"
792
777
793 #: rhodecode/lib/helpers.py:620
778 #: rhodecode/lib/helpers.py:620
794 #: rhodecode/templates/pullrequests/pullrequest_show.html:4
779 #: rhodecode/templates/pullrequests/pullrequest_show.html:4
795 #: rhodecode/templates/pullrequests/pullrequest_show.html:12
780 #: rhodecode/templates/pullrequests/pullrequest_show.html:12
796 #, python-format
781 #, python-format
797 msgid "Pull request #%s"
782 msgid "Pull request #%s"
798 msgstr ""
783 msgstr "拉取请求 #%s"
799
784
800 #: rhodecode/lib/helpers.py:626
785 #: rhodecode/lib/helpers.py:626
801 msgid "[deleted] repository"
786 msgid "[deleted] repository"
802 msgstr ""
787 msgstr "[删除] 版本库"
803
788
804 #: rhodecode/lib/helpers.py:628 rhodecode/lib/helpers.py:638
789 #: rhodecode/lib/helpers.py:628 rhodecode/lib/helpers.py:638
805 msgid "[created] repository"
790 msgid "[created] repository"
806 msgstr ""
791 msgstr "[创建] 版本库"
807
792
808 #: rhodecode/lib/helpers.py:630
793 #: rhodecode/lib/helpers.py:630
809 #, fuzzy
810 msgid "[created] repository as fork"
794 msgid "[created] repository as fork"
811 msgstr "建立版本库 %s"
795 msgstr "[创建] 分支版本库"
812
796
813 #: rhodecode/lib/helpers.py:632 rhodecode/lib/helpers.py:640
797 #: rhodecode/lib/helpers.py:632 rhodecode/lib/helpers.py:640
814 msgid "[forked] repository"
798 msgid "[forked] repository"
815 msgstr ""
799 msgstr "[分支] 版本库"
816
800
817 #: rhodecode/lib/helpers.py:634 rhodecode/lib/helpers.py:642
801 #: rhodecode/lib/helpers.py:634 rhodecode/lib/helpers.py:642
818 msgid "[updated] repository"
802 msgid "[updated] repository"
819 msgstr ""
803 msgstr "[更新] 版本库"
820
804
821 #: rhodecode/lib/helpers.py:636
805 #: rhodecode/lib/helpers.py:636
822 msgid "[delete] repository"
806 msgid "[delete] repository"
823 msgstr ""
807 msgstr "[删除] 版本库"
824
808
825 #: rhodecode/lib/helpers.py:644
809 #: rhodecode/lib/helpers.py:644
826 #, fuzzy
827 msgid "[created] user"
810 msgid "[created] user"
828 msgstr "创建用户 %s"
811 msgstr "[创建] 用户"
829
812
830 #: rhodecode/lib/helpers.py:646
813 #: rhodecode/lib/helpers.py:646
831 #, fuzzy
832 msgid "[updated] user"
814 msgid "[updated] user"
833 msgstr "更新用户组 %s"
815 msgstr "[更新] 用户"
834
816
835 #: rhodecode/lib/helpers.py:648
817 #: rhodecode/lib/helpers.py:648
836 #, fuzzy
837 msgid "[created] users group"
818 msgid "[created] users group"
838 msgstr "建立用户组 %s"
819 msgstr "[创建] 用户组"
839
820
840 #: rhodecode/lib/helpers.py:650
821 #: rhodecode/lib/helpers.py:650
841 #, fuzzy
842 msgid "[updated] users group"
822 msgid "[updated] users group"
843 msgstr "更新用户组 %s"
823 msgstr "[更新] 用户组"
844
824
845 #: rhodecode/lib/helpers.py:652
825 #: rhodecode/lib/helpers.py:652
846 msgid "[commented] on revision in repository"
826 msgid "[commented] on revision in repository"
847 msgstr ""
827 msgstr "[评论] 了版本库中的修订"
848
828
849 #: rhodecode/lib/helpers.py:654
829 #: rhodecode/lib/helpers.py:654
850 #, fuzzy
851 msgid "[commented] on pull request for"
830 msgid "[commented] on pull request for"
852 msgstr "创建用户 %s"
831 msgstr "[评论] 拉取请求"
853
832
854 #: rhodecode/lib/helpers.py:656
833 #: rhodecode/lib/helpers.py:656
855 msgid "[closed] pull request for"
834 msgid "[closed] pull request for"
856 msgstr ""
835 msgstr "[关闭] 拉取请求"
857
836
858 #: rhodecode/lib/helpers.py:658
837 #: rhodecode/lib/helpers.py:658
859 msgid "[pushed] into"
838 msgid "[pushed] into"
860 msgstr ""
839 msgstr "[推送] 到"
861
840
862 #: rhodecode/lib/helpers.py:660
841 #: rhodecode/lib/helpers.py:660
863 msgid "[committed via RhodeCode] into repository"
842 msgid "[committed via RhodeCode] into repository"
864 msgstr ""
843 msgstr "[通过 RhodeCode 提交] 到版本库"
865
844
866 #: rhodecode/lib/helpers.py:662
845 #: rhodecode/lib/helpers.py:662
867 msgid "[pulled from remote] into repository"
846 msgid "[pulled from remote] into repository"
868 msgstr ""
847 msgstr "[远程拉取] 到版本库"
869
848
870 #: rhodecode/lib/helpers.py:664
849 #: rhodecode/lib/helpers.py:664
871 msgid "[pulled] from"
850 msgid "[pulled] from"
872 msgstr ""
851 msgstr "[拉取] 自"
873
852
874 #: rhodecode/lib/helpers.py:666
853 #: rhodecode/lib/helpers.py:666
875 msgid "[started following] repository"
854 msgid "[started following] repository"
876 msgstr ""
855 msgstr "[开始关注] 版本库"
877
856
878 #: rhodecode/lib/helpers.py:668
857 #: rhodecode/lib/helpers.py:668
879 msgid "[stopped following] repository"
858 msgid "[stopped following] repository"
880 msgstr ""
859 msgstr "[停止关注] 版本库"
881
860
882 #: rhodecode/lib/helpers.py:840
861 #: rhodecode/lib/helpers.py:840
883 #, python-format
862 #, python-format
884 msgid " and %s more"
863 msgid " and %s more"
885 msgstr ""
864 msgstr "还有 %s 个"
886
865
887 #: rhodecode/lib/helpers.py:844
866 #: rhodecode/lib/helpers.py:844
888 msgid "No Files"
867 msgid "No Files"
889 msgstr "没有文件"
868 msgstr "没有文件"
890
869
891 #: rhodecode/lib/utils2.py:335
870 #: rhodecode/lib/utils2.py:335
892 #, fuzzy, python-format
871 #, python-format
893 msgid "%d year"
872 msgid "%d year"
894 msgid_plural "%d years"
873 msgid_plural "%d years"
895 msgstr[0] "年"
874 msgstr[0] "%d 年"
896
875
897 #: rhodecode/lib/utils2.py:336
876 #: rhodecode/lib/utils2.py:336
898 #, fuzzy, python-format
877 #, python-format
899 msgid "%d month"
878 msgid "%d month"
900 msgid_plural "%d months"
879 msgid_plural "%d months"
901 msgstr[0] "月"
880 msgstr[0] "%d 月"
902
881
903 #: rhodecode/lib/utils2.py:337
882 #: rhodecode/lib/utils2.py:337
904 #, fuzzy, python-format
883 #, python-format
905 msgid "%d day"
884 msgid "%d day"
906 msgid_plural "%d days"
885 msgid_plural "%d days"
907 msgstr[0] ""
886 msgstr[0] "%d 天"
908
887
909 #: rhodecode/lib/utils2.py:338
888 #: rhodecode/lib/utils2.py:338
910 #, fuzzy, python-format
889 #, python-format
911 msgid "%d hour"
890 msgid "%d hour"
912 msgid_plural "%d hours"
891 msgid_plural "%d hours"
913 msgstr[0] "时"
892 msgstr[0] "%d 小时"
914
893
915 #: rhodecode/lib/utils2.py:339
894 #: rhodecode/lib/utils2.py:339
916 #, fuzzy, python-format
895 #, python-format
917 msgid "%d minute"
896 msgid "%d minute"
918 msgid_plural "%d minutes"
897 msgid_plural "%d minutes"
919 msgstr[0] "分"
898 msgstr[0] "%d "
920
899
921 #: rhodecode/lib/utils2.py:340
900 #: rhodecode/lib/utils2.py:340
922 #, fuzzy, python-format
901 #, python-format
923 msgid "%d second"
902 msgid "%d second"
924 msgid_plural "%d seconds"
903 msgid_plural "%d seconds"
925 msgstr[0] "秒"
904 msgstr[0] "%d 秒"
926
905
927 #: rhodecode/lib/utils2.py:355
906 #: rhodecode/lib/utils2.py:355
928 #, fuzzy, python-format
907 #, python-format
929 msgid "%s ago"
908 msgid "%s ago"
930 msgstr "之前"
909 msgstr "%s 之前"
931
910
932 #: rhodecode/lib/utils2.py:357
911 #: rhodecode/lib/utils2.py:357
933 #, python-format
912 #, python-format
934 msgid "%s and %s ago"
913 msgid "%s and %s ago"
935 msgstr ""
914 msgstr "%s 零 %s 之前"
936
915
937 #: rhodecode/lib/utils2.py:360
916 #: rhodecode/lib/utils2.py:360
938 msgid "just now"
917 msgid "just now"
939 msgstr "现在"
918 msgstr "刚才"
940
919
941 #: rhodecode/lib/celerylib/tasks.py:269
920 #: rhodecode/lib/celerylib/tasks.py:269
942 #, fuzzy
943 msgid "password reset link"
921 msgid "password reset link"
944 msgstr "密码重置链接已经发送"
922 msgstr "密码重置链接"
945
923
946 #: rhodecode/model/comment.py:110
924 #: rhodecode/model/comment.py:110
947 #, python-format
925 #, python-format
948 msgid "on line %s"
926 msgid "on line %s"
949 msgstr ""
927 msgstr "在 %s 行"
950
928
951 #: rhodecode/model/comment.py:157
929 #: rhodecode/model/comment.py:157
952 msgid "[Mention]"
930 msgid "[Mention]"
953 msgstr ""
931 msgstr "[提及]"
954
932
955 #: rhodecode/model/db.py:1140
933 #: rhodecode/model/db.py:1140
956 #, fuzzy
957 msgid "Repository no access"
934 msgid "Repository no access"
958 msgstr "个版本库"
935 msgstr "无版本库访问权限"
959
936
960 #: rhodecode/model/db.py:1141
937 #: rhodecode/model/db.py:1141
961 #, fuzzy
962 msgid "Repository read access"
938 msgid "Repository read access"
963 msgstr "这个版本库已经存在"
939 msgstr "版本库读取权限"
964
940
965 #: rhodecode/model/db.py:1142
941 #: rhodecode/model/db.py:1142
966 #, fuzzy
967 msgid "Repository write access"
942 msgid "Repository write access"
968 msgstr "版本库"
943 msgstr "版本库写入权限"
969
944
970 #: rhodecode/model/db.py:1143
945 #: rhodecode/model/db.py:1143
971 #, fuzzy
972 msgid "Repository admin access"
946 msgid "Repository admin access"
973 msgstr "版本库"
947 msgstr "版本库管理权限"
974
948
975 #: rhodecode/model/db.py:1145
949 #: rhodecode/model/db.py:1145
976 #, fuzzy
977 msgid "Repositories Group no access"
950 msgid "Repositories Group no access"
978 msgstr "版本库组"
951 msgstr "版本库组访问权限"
979
952
980 #: rhodecode/model/db.py:1146
953 #: rhodecode/model/db.py:1146
981 #, fuzzy
982 msgid "Repositories Group read access"
954 msgid "Repositories Group read access"
983 msgstr "版本库组"
955 msgstr "版本库组读取权限"
984
956
985 #: rhodecode/model/db.py:1147
957 #: rhodecode/model/db.py:1147
986 #, fuzzy
987 msgid "Repositories Group write access"
958 msgid "Repositories Group write access"
988 msgstr "版本库组"
959 msgstr "版本库组写入"
989
960
990 #: rhodecode/model/db.py:1148
961 #: rhodecode/model/db.py:1148
991 #, fuzzy
992 msgid "Repositories Group admin access"
962 msgid "Repositories Group admin access"
993 msgstr "版本库组"
963 msgstr "版本库组管理权限"
994
964
995 #: rhodecode/model/db.py:1150
965 #: rhodecode/model/db.py:1150
996 #, fuzzy
997 msgid "RhodeCode Administrator"
966 msgid "RhodeCode Administrator"
998 msgstr "用户管理员"
967 msgstr "RhodeCode 管理员"
999
968
1000 #: rhodecode/model/db.py:1151
969 #: rhodecode/model/db.py:1151
1001 #, fuzzy
1002 msgid "Repository creation disabled"
970 msgid "Repository creation disabled"
1003 msgstr "建版本库"
971 msgstr "禁用创建版本库"
1004
972
1005 #: rhodecode/model/db.py:1152
973 #: rhodecode/model/db.py:1152
1006 #, fuzzy
1007 msgid "Repository creation enabled"
974 msgid "Repository creation enabled"
1008 msgstr "建版本库"
975 msgstr "允许创建版本库"
1009
976
1010 #: rhodecode/model/db.py:1153
977 #: rhodecode/model/db.py:1153
1011 #, fuzzy
1012 msgid "Repository forking disabled"
978 msgid "Repository forking disabled"
1013 msgstr "建立版本库"
979 msgstr "禁用分支 版本库"
1014
980
1015 #: rhodecode/model/db.py:1154
981 #: rhodecode/model/db.py:1154
1016 #, fuzzy
1017 msgid "Repository forking enabled"
982 msgid "Repository forking enabled"
1018 msgstr "建立版本库"
983 msgstr "允许分支版本库"
1019
984
1020 #: rhodecode/model/db.py:1155
985 #: rhodecode/model/db.py:1155
1021 #, fuzzy
1022 msgid "Register disabled"
986 msgid "Register disabled"
1023 msgstr "禁用"
987 msgstr "禁用注册"
1024
988
1025 #: rhodecode/model/db.py:1156
989 #: rhodecode/model/db.py:1156
1026 msgid "Register new user with RhodeCode with manual activation"
990 msgid "Register new user with RhodeCode with manual activation"
1027 msgstr ""
991 msgstr "用手动激活注册新用户"
1028
992
1029 #: rhodecode/model/db.py:1159
993 #: rhodecode/model/db.py:1159
1030 msgid "Register new user with RhodeCode with auto activation"
994 msgid "Register new user with RhodeCode with auto activation"
1031 msgstr ""
995 msgstr "用自动激活注册新用户"
1032
996
1033 #: rhodecode/model/db.py:1579
997 #: rhodecode/model/db.py:1579
1034 msgid "Not Reviewed"
998 msgid "Not Reviewed"
1035 msgstr ""
999 msgstr "未检视"
1036
1000
1037 #: rhodecode/model/db.py:1580
1001 #: rhodecode/model/db.py:1580
1038 #, fuzzy
1039 msgid "Approved"
1002 msgid "Approved"
1040 msgstr "移除"
1003 msgstr "已批准"
1041
1004
1042 #: rhodecode/model/db.py:1581
1005 #: rhodecode/model/db.py:1581
1043 msgid "Rejected"
1006 msgid "Rejected"
1044 msgstr ""
1007 msgstr "驳回"
1045
1008
1046 #: rhodecode/model/db.py:1582
1009 #: rhodecode/model/db.py:1582
1047 msgid "Under Review"
1010 msgid "Under Review"
1048 msgstr ""
1011 msgstr "检视中"
1049
1012
1050 #: rhodecode/model/forms.py:43
1013 #: rhodecode/model/forms.py:43
1051 msgid "Please enter a login"
1014 msgid "Please enter a login"
1052 msgstr "请登录"
1015 msgstr "请登录"
1053
1016
1054 #: rhodecode/model/forms.py:44
1017 #: rhodecode/model/forms.py:44
1055 #, python-format
1018 #, python-format
1056 msgid "Enter a value %(min)i characters long or more"
1019 msgid "Enter a value %(min)i characters long or more"
1057 msgstr ""
1020 msgstr "输入一个不少于 %(min)i 个字符的值"
1058
1021
1059 #: rhodecode/model/forms.py:52
1022 #: rhodecode/model/forms.py:52
1060 msgid "Please enter a password"
1023 msgid "Please enter a password"
1061 msgstr "请输入密码"
1024 msgstr "请输入密码"
1062
1025
1063 #: rhodecode/model/forms.py:53
1026 #: rhodecode/model/forms.py:53
1064 #, python-format
1027 #, python-format
1065 msgid "Enter %(min)i characters or more"
1028 msgid "Enter %(min)i characters or more"
1066 msgstr ""
1029 msgstr "输入少于 %(min)i 个字符"
1067
1030
1068 #: rhodecode/model/notification.py:220
1031 #: rhodecode/model/notification.py:220
1069 msgid "commented on commit"
1032 msgid "commented on commit"
1070 msgstr ""
1033 msgstr "评论了评论"
1071
1034
1072 #: rhodecode/model/notification.py:221
1035 #: rhodecode/model/notification.py:221
1073 #, fuzzy
1074 msgid "sent message"
1036 msgid "sent message"
1075 msgstr "提交信息"
1037 msgstr "发送信息"
1076
1038
1077 #: rhodecode/model/notification.py:222
1039 #: rhodecode/model/notification.py:222
1078 msgid "mentioned you"
1040 msgid "mentioned you"
1079 msgstr ""
1041 msgstr "提到了你"
1080
1042
1081 #: rhodecode/model/notification.py:223
1043 #: rhodecode/model/notification.py:223
1082 #, fuzzy
1083 msgid "registered in RhodeCode"
1044 msgid "registered in RhodeCode"
1084 msgstr "成功注册到 rhodecode"
1045 msgstr "注册到 RhodeCode"
1085
1046
1086 #: rhodecode/model/notification.py:224
1047 #: rhodecode/model/notification.py:224
1087 msgid "opened new pull request"
1048 msgid "opened new pull request"
1088 msgstr ""
1049 msgstr "创建新的拉取请求"
1089
1050
1090 #: rhodecode/model/notification.py:225
1051 #: rhodecode/model/notification.py:225
1091 msgid "commented on pull request"
1052 msgid "commented on pull request"
1092 msgstr ""
1053 msgstr "评论了拉取请求"
1093
1054
1094 #: rhodecode/model/pull_request.py:84
1055 #: rhodecode/model/pull_request.py:84
1095 #, python-format
1056 #, python-format
1096 msgid "%(user)s wants you to review pull request #%(pr_id)s"
1057 msgid "%(user)s wants you to review pull request #%(pr_id)s"
1097 msgstr ""
1058 msgstr "%(user)s 想要你检视拉取请求 #%(pr_id)s"
1098
1059
1099 #: rhodecode/model/scm.py:535
1060 #: rhodecode/model/scm.py:535
1100 #, fuzzy
1101 msgid "latest tip"
1061 msgid "latest tip"
1102 msgstr "最后登录"
1062 msgstr "最后 tip 版本"
1103
1063
1104 #: rhodecode/model/user.py:230
1064 #: rhodecode/model/user.py:230
1105 #, fuzzy
1106 msgid "new user registration"
1065 msgid "new user registration"
1107 msgstr "[RhodeCode] 新用户注册"
1066 msgstr "[RhodeCode] 新用户注册"
1108
1067
1109 #: rhodecode/model/user.py:255 rhodecode/model/user.py:277
1068 #: rhodecode/model/user.py:255 rhodecode/model/user.py:277
1110 #: rhodecode/model/user.py:299
1069 #: rhodecode/model/user.py:299
1111 msgid "You can't Edit this user since it's crucial for entire application"
1070 msgid "You can't Edit this user since it's crucial for entire application"
1112 msgstr "由于是系统帐号,无法编辑该用户"
1071 msgstr "由于是系统帐号,无法编辑该用户"
1113
1072
1114 #: rhodecode/model/user.py:323
1073 #: rhodecode/model/user.py:323
1115 msgid "You can't remove this user since it's crucial for entire application"
1074 msgid "You can't remove this user since it's crucial for entire application"
1116 msgstr "由于是系统帐号,无法删除该用户"
1075 msgstr "由于是系统帐号,无法删除该用户"
1117
1076
1118 #: rhodecode/model/user.py:329
1077 #: rhodecode/model/user.py:329
1119 #, fuzzy, python-format
1078 #, python-format
1120 msgid ""
1079 msgid ""
1121 "user \"%s\" still owns %s repositories and cannot be removed. Switch "
1080 "user \"%s\" still owns %s repositories and cannot be removed. Switch owners "
1122 "owners or remove those repositories. %s"
1081 "or remove those repositories. %s"
1123 msgstr "由于该用户拥有版本库 %s 因而无法删除,请变更版本库所有者或删除版本库"
1082 msgstr ""
1083 "由于用户 \"%s\" 拥有版本库 %s 因而无法删除,请修改版本库所有者或删除版本"
1084 "库。%s"
1124
1085
1125 #: rhodecode/model/validators.py:35 rhodecode/model/validators.py:36
1086 #: rhodecode/model/validators.py:35 rhodecode/model/validators.py:36
1126 msgid "Value cannot be an empty list"
1087 msgid "Value cannot be an empty list"
1127 msgstr ""
1088 msgstr "值不能为空"
1128
1089
1129 #: rhodecode/model/validators.py:82
1090 #: rhodecode/model/validators.py:82
1130 #, fuzzy, python-format
1091 #, python-format
1131 msgid "Username \"%(username)s\" already exists"
1092 msgid "Username \"%(username)s\" already exists"
1132 msgstr "用户名已经存在"
1093 msgstr "用户名称 %(username)s 已经存在"
1133
1094
1134 #: rhodecode/model/validators.py:84
1095 #: rhodecode/model/validators.py:84
1135 #, python-format
1096 #, python-format
1136 msgid "Username \"%(username)s\" is forbidden"
1097 msgid "Username \"%(username)s\" is forbidden"
1137 msgstr ""
1098 msgstr "不允许用户名 \"%(username)s\""
1138
1099
1139 #: rhodecode/model/validators.py:86
1100 #: rhodecode/model/validators.py:86
1140 msgid ""
1101 msgid ""
1141 "Username may only contain alphanumeric characters underscores, periods or"
1102 "Username may only contain alphanumeric characters underscores, periods or "
1142 " dashes and must begin with alphanumeric character"
1103 "dashes and must begin with alphanumeric character"
1143 msgstr "只能使用字母、数字、下划线、小数点或减号作为用户名,且必须由数字或字母开头"
1104 msgstr ""
1105 "只能使用字母、数字、下划线、小数点或减号作为用户名,且必须由数字或字母开头"
1144
1106
1145 #: rhodecode/model/validators.py:114
1107 #: rhodecode/model/validators.py:114
1146 #, fuzzy, python-format
1108 #, python-format
1147 msgid "Username %(username)s is not valid"
1109 msgid "Username %(username)s is not valid"
1148 msgstr "用户或用户组名称无效"
1110 msgstr "用户名称 %(username)s 无效"
1149
1111
1150 #: rhodecode/model/validators.py:133
1112 #: rhodecode/model/validators.py:133
1151 #, fuzzy
1152 msgid "Invalid users group name"
1113 msgid "Invalid users group name"
1153 msgstr "无效用户名"
1114 msgstr "无效用户名"
1154
1115
1155 #: rhodecode/model/validators.py:134
1116 #: rhodecode/model/validators.py:134
1156 #, fuzzy, python-format
1117 #, python-format
1157 msgid "Users group \"%(usersgroup)s\" already exists"
1118 msgid "Users group \"%(usersgroup)s\" already exists"
1158 msgstr "该用户组名称已经存在"
1119 msgstr "用户组 \"%(usersgroup)s\" 已经存在"
1159
1120
1160 #: rhodecode/model/validators.py:136
1121 #: rhodecode/model/validators.py:136
1161 msgid ""
1122 msgid ""
1162 "users group name may only contain alphanumeric characters underscores, "
1123 "users group name may only contain alphanumeric characters underscores, "
1163 "periods or dashes and must begin with alphanumeric character"
1124 "periods or dashes and must begin with alphanumeric character"
1164 msgstr "只能使用字母、数字、下划线、小数点或减号作为用户组名,且必须由数字或字母开头"
1125 msgstr ""
1126 "只能使用字母、数字、下划线、小数点或减号作为用户组名,且必须由数字或字母开头"
1165
1127
1166 #: rhodecode/model/validators.py:174
1128 #: rhodecode/model/validators.py:174
1167 msgid "Cannot assign this group as parent"
1129 msgid "Cannot assign this group as parent"
1168 msgstr ""
1130 msgstr "不能将这个组作为 parent"
1169
1131
1170 #: rhodecode/model/validators.py:175
1132 #: rhodecode/model/validators.py:175
1171 #, fuzzy, python-format
1133 #, python-format
1172 msgid "Group \"%(group_name)s\" already exists"
1134 msgid "Group \"%(group_name)s\" already exists"
1173 msgstr "该用户名已经存在"
1135 msgstr "组 \"%(group_name)s\" 已经存在"
1174
1136
1175 #: rhodecode/model/validators.py:177
1137 #: rhodecode/model/validators.py:177
1176 #, fuzzy, python-format
1138 #, python-format
1177 msgid "Repository with name \"%(group_name)s\" already exists"
1139 msgid "Repository with name \"%(group_name)s\" already exists"
1178 msgstr "这个版本库已经存在"
1140 msgstr "已经存在名为 \"%(group_name)s\" 的版本库"
1179
1141
1180 #: rhodecode/model/validators.py:235
1142 #: rhodecode/model/validators.py:235
1181 #, fuzzy
1182 msgid "Invalid characters (non-ascii) in password"
1143 msgid "Invalid characters (non-ascii) in password"
1183 msgstr "密码含有无效字符"
1144 msgstr "密码含有无效(非ASCII)字符"
1184
1145
1185 #: rhodecode/model/validators.py:250
1146 #: rhodecode/model/validators.py:250
1186 msgid "Passwords do not match"
1147 msgid "Passwords do not match"
1187 msgstr "密码不符"
1148 msgstr "密码不符"
1188
1149
1189 #: rhodecode/model/validators.py:267
1150 #: rhodecode/model/validators.py:267
1190 msgid "invalid password"
1151 msgid "invalid password"
1191 msgstr "无效密码"
1152 msgstr "无效密码"
1192
1153
1193 #: rhodecode/model/validators.py:268
1154 #: rhodecode/model/validators.py:268
1194 msgid "invalid user name"
1155 msgid "invalid user name"
1195 msgstr "无效用户名"
1156 msgstr "无效用户名"
1196
1157
1197 #: rhodecode/model/validators.py:269
1158 #: rhodecode/model/validators.py:269
1198 msgid "Your account is disabled"
1159 msgid "Your account is disabled"
1199 msgstr "该帐号已被禁用"
1160 msgstr "该帐号已被禁用"
1200
1161
1201 #: rhodecode/model/validators.py:313
1162 #: rhodecode/model/validators.py:313
1202 #, fuzzy, python-format
1163 #, python-format
1203 msgid "Repository name %(repo)s is disallowed"
1164 msgid "Repository name %(repo)s is disallowed"
1204 msgstr "该版本库名称被禁用"
1165 msgstr "版本库名称不能为 %(repo)s"
1205
1166
1206 #: rhodecode/model/validators.py:315
1167 #: rhodecode/model/validators.py:315
1207 #, fuzzy, python-format
1168 #, python-format
1208 msgid "Repository named %(repo)s already exists"
1169 msgid "Repository named %(repo)s already exists"
1209 msgstr "这个版本库已经存在"
1170 msgstr "已经存在版本库 %(repo)s"
1210
1171
1211 #: rhodecode/model/validators.py:316
1172 #: rhodecode/model/validators.py:316
1212 #, fuzzy, python-format
1173 #, python-format
1213 msgid "Repository \"%(repo)s\" already exists in group \"%(group)s\""
1174 msgid "Repository \"%(repo)s\" already exists in group \"%(group)s\""
1214 msgstr "组中已经存在该版本库"
1175 msgstr "版本库组 \"%(group)s\" 中已经存在版本库 \"%(repo)s\""
1215
1176
1216 #: rhodecode/model/validators.py:318
1177 #: rhodecode/model/validators.py:318
1217 #, fuzzy, python-format
1178 #, python-format
1218 msgid "Repositories group with name \"%(repo)s\" already exists"
1179 msgid "Repositories group with name \"%(repo)s\" already exists"
1219 msgstr "这个版本库已经存在"
1180 msgstr "已经存在名为 \"%(repo)s\" 的版本库组"
1220
1181
1221 #: rhodecode/model/validators.py:431
1182 #: rhodecode/model/validators.py:431
1222 msgid "invalid clone url"
1183 msgid "invalid clone url"
1223 msgstr "无效的 clone 地址"
1184 msgstr "无效的克隆地址"
1224
1185
1225 #: rhodecode/model/validators.py:432
1186 #: rhodecode/model/validators.py:432
1226 msgid "Invalid clone url, provide a valid clone http(s)/svn+http(s) url"
1187 msgid "Invalid clone url, provide a valid clone http(s)/svn+http(s) url"
1227 msgstr ""
1188 msgstr "无效的克隆地址,提供一个有效的克隆 http(s) 或 svn+http(s) 地址"
1228
1189
1229 #: rhodecode/model/validators.py:457
1190 #: rhodecode/model/validators.py:457
1230 #, fuzzy
1231 msgid "Fork have to be the same type as parent"
1191 msgid "Fork have to be the same type as parent"
1232 msgstr "分支必须使用相同的版本库类型"
1192 msgstr "分支必须使用和父版本库相同的类型"
1233
1193
1234 #: rhodecode/model/validators.py:478
1194 #: rhodecode/model/validators.py:478
1235 msgid "This username or users group name is not valid"
1195 msgid "This username or users group name is not valid"
1236 msgstr "用户或用户组名称无效"
1196 msgstr "用户或用户组名称无效"
1237
1197
1238 #: rhodecode/model/validators.py:562
1198 #: rhodecode/model/validators.py:562
1239 msgid "This is not a valid path"
1199 msgid "This is not a valid path"
1240 msgstr "不是一个合法的路径"
1200 msgstr "不是一个合法的路径"
1241
1201
1242 #: rhodecode/model/validators.py:577
1202 #: rhodecode/model/validators.py:577
1243 msgid "This e-mail address is already taken"
1203 msgid "This e-mail address is already taken"
1244 msgstr "该邮件地址已被使用"
1204 msgstr "该邮件地址已被使用"
1245
1205
1246 #: rhodecode/model/validators.py:597
1206 #: rhodecode/model/validators.py:597
1247 #, fuzzy, python-format
1207 #, python-format
1248 msgid "e-mail \"%(email)s\" does not exist."
1208 msgid "e-mail \"%(email)s\" does not exist."
1249 msgstr "邮件地址不存在"
1209 msgstr "邮件地址 \"%(email)s\" 不存在"
1250
1210
1251 #: rhodecode/model/validators.py:634
1211 #: rhodecode/model/validators.py:634
1252 msgid ""
1212 msgid ""
1253 "The LDAP Login attribute of the CN must be specified - this is the name "
1213 "The LDAP Login attribute of the CN must be specified - this is the name of "
1254 "of the attribute that is equivalent to \"username\""
1214 "the attribute that is equivalent to \"username\""
1255 msgstr ""
1215 msgstr "LDAP 登陆属性的 CN 必须指定 - 这个名字作为用户名"
1256
1216
1257 #: rhodecode/model/validators.py:653
1217 #: rhodecode/model/validators.py:653
1258 #, python-format
1218 #, python-format
1259 msgid "Revisions %(revs)s are already part of pull request or have set status"
1219 msgid "Revisions %(revs)s are already part of pull request or have set status"
1260 msgstr ""
1220 msgstr "修订 %(revs)s 已经包含在拉取请求中或者或者已经设置状态"
1261
1221
1262 #: rhodecode/templates/index.html:3
1222 #: rhodecode/templates/index.html:3
1263 msgid "Dashboard"
1223 msgid "Dashboard"
1264 msgstr ""
1224 msgstr "控制面板"
1265
1225
1266 #: rhodecode/templates/index_base.html:6
1226 #: rhodecode/templates/index_base.html:6
1267 #: rhodecode/templates/repo_switcher_list.html:4
1227 #: rhodecode/templates/repo_switcher_list.html:4
1268 #: rhodecode/templates/admin/repos/repos.html:9
1228 #: rhodecode/templates/admin/repos/repos.html:9
1269 #: rhodecode/templates/admin/users/user_edit_my_account.html:31
1229 #: rhodecode/templates/admin/users/user_edit_my_account.html:31
1270 #: rhodecode/templates/admin/users/users.html:9
1230 #: rhodecode/templates/admin/users/users.html:9
1271 #: rhodecode/templates/bookmarks/bookmarks.html:10
1231 #: rhodecode/templates/bookmarks/bookmarks.html:10
1272 #: rhodecode/templates/branches/branches.html:9
1232 #: rhodecode/templates/branches/branches.html:9
1273 #: rhodecode/templates/journal/journal.html:40
1233 #: rhodecode/templates/journal/journal.html:40
1274 #: rhodecode/templates/tags/tags.html:10
1234 #: rhodecode/templates/tags/tags.html:10
1275 msgid "quick filter..."
1235 msgid "quick filter..."
1276 msgstr "快速过滤..."
1236 msgstr "快速过滤..."
1277
1237
1278 #: rhodecode/templates/index_base.html:6
1238 #: rhodecode/templates/index_base.html:6
1279 #: rhodecode/templates/admin/repos/repos.html:9
1239 #: rhodecode/templates/admin/repos/repos.html:9
1280 #: rhodecode/templates/base/base.html:221
1240 #: rhodecode/templates/base/base.html:221
1281 msgid "repositories"
1241 msgid "repositories"
1282 msgstr "个版本库"
1242 msgstr "个版本库"
1283
1243
1284 #: rhodecode/templates/index_base.html:13
1244 #: rhodecode/templates/index_base.html:13
1285 #: rhodecode/templates/index_base.html:15
1245 #: rhodecode/templates/index_base.html:15
1286 #: rhodecode/templates/admin/repos/repos.html:21
1246 #: rhodecode/templates/admin/repos/repos.html:21
1287 msgid "ADD REPOSITORY"
1247 msgid "ADD REPOSITORY"
1288 msgstr "新版本库"
1248 msgstr "新版本库"
1289
1249
1290 #: rhodecode/templates/index_base.html:29
1250 #: rhodecode/templates/index_base.html:29
1291 #: rhodecode/templates/admin/repos_groups/repos_groups_add.html:32
1251 #: rhodecode/templates/admin/repos_groups/repos_groups_add.html:32
1292 #: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:32
1252 #: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:32
1293 #: rhodecode/templates/admin/repos_groups/repos_groups_show.html:33
1253 #: rhodecode/templates/admin/repos_groups/repos_groups_show.html:33
1294 #: rhodecode/templates/admin/users_groups/users_group_add.html:32
1254 #: rhodecode/templates/admin/users_groups/users_group_add.html:32
1295 #: rhodecode/templates/admin/users_groups/users_group_edit.html:33
1255 #: rhodecode/templates/admin/users_groups/users_group_edit.html:33
1296 msgid "Group name"
1256 msgid "Group name"
1297 msgstr "组名"
1257 msgstr "组名"
1298
1258
1299 #: rhodecode/templates/index_base.html:30
1259 #: rhodecode/templates/index_base.html:30
1300 #: rhodecode/templates/index_base.html:71
1260 #: rhodecode/templates/index_base.html:71
1301 #: rhodecode/templates/index_base.html:142
1261 #: rhodecode/templates/index_base.html:142
1302 #: rhodecode/templates/index_base.html:168
1262 #: rhodecode/templates/index_base.html:168
1303 #: rhodecode/templates/admin/repos/repo_add_base.html:56
1263 #: rhodecode/templates/admin/repos/repo_add_base.html:56
1304 #: rhodecode/templates/admin/repos/repo_edit.html:75
1264 #: rhodecode/templates/admin/repos/repo_edit.html:75
1305 #: rhodecode/templates/admin/repos/repos.html:72
1265 #: rhodecode/templates/admin/repos/repos.html:72
1306 #: rhodecode/templates/admin/repos_groups/repos_groups_add.html:41
1266 #: rhodecode/templates/admin/repos_groups/repos_groups_add.html:41
1307 #: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:41
1267 #: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:41
1308 #: rhodecode/templates/admin/repos_groups/repos_groups_show.html:34
1268 #: rhodecode/templates/admin/repos_groups/repos_groups_show.html:34
1309 #: rhodecode/templates/forks/fork.html:59
1269 #: rhodecode/templates/forks/fork.html:59
1310 #: rhodecode/templates/settings/repo_settings.html:66
1270 #: rhodecode/templates/settings/repo_settings.html:66
1311 #: rhodecode/templates/summary/summary.html:105
1271 #: rhodecode/templates/summary/summary.html:105
1312 msgid "Description"
1272 msgid "Description"
1313 msgstr "描述"
1273 msgstr "描述"
1314
1274
1315 #: rhodecode/templates/index_base.html:40
1275 #: rhodecode/templates/index_base.html:40
1316 #: rhodecode/templates/admin/repos_groups/repos_groups_show.html:46
1276 #: rhodecode/templates/admin/repos_groups/repos_groups_show.html:46
1317 msgid "Repositories group"
1277 msgid "Repositories group"
1318 msgstr "版本库组"
1278 msgstr "版本库组"
1319
1279
1320 #: rhodecode/templates/index_base.html:70
1280 #: rhodecode/templates/index_base.html:70
1321 #: rhodecode/templates/index_base.html:166
1281 #: rhodecode/templates/index_base.html:166
1322 #: rhodecode/templates/admin/repos/repo_add_base.html:9
1282 #: rhodecode/templates/admin/repos/repo_add_base.html:9
1323 #: rhodecode/templates/admin/repos/repo_edit.html:32
1283 #: rhodecode/templates/admin/repos/repo_edit.html:32
1324 #: rhodecode/templates/admin/repos/repos.html:70
1284 #: rhodecode/templates/admin/repos/repos.html:70
1325 #: rhodecode/templates/admin/users/user_edit.html:192
1285 #: rhodecode/templates/admin/users/user_edit.html:192
1326 #: rhodecode/templates/admin/users/user_edit_my_account.html:59
1286 #: rhodecode/templates/admin/users/user_edit_my_account.html:59
1327 #: rhodecode/templates/admin/users/user_edit_my_account.html:157
1287 #: rhodecode/templates/admin/users/user_edit_my_account.html:157
1328 #: rhodecode/templates/admin/users/user_edit_my_account.html:193
1288 #: rhodecode/templates/admin/users/user_edit_my_account.html:193
1329 #: rhodecode/templates/admin/users/user_edit_my_account_repos.html:6
1289 #: rhodecode/templates/admin/users/user_edit_my_account_repos.html:6
1330 #: rhodecode/templates/bookmarks/bookmarks.html:36
1290 #: rhodecode/templates/bookmarks/bookmarks.html:36
1331 #: rhodecode/templates/bookmarks/bookmarks_data.html:6
1291 #: rhodecode/templates/bookmarks/bookmarks_data.html:6
1332 #: rhodecode/templates/branches/branches.html:51
1292 #: rhodecode/templates/branches/branches.html:51
1333 #: rhodecode/templates/files/files_browser.html:47
1293 #: rhodecode/templates/files/files_browser.html:47
1334 #: rhodecode/templates/journal/journal.html:59
1294 #: rhodecode/templates/journal/journal.html:59
1335 #: rhodecode/templates/journal/journal.html:107
1295 #: rhodecode/templates/journal/journal.html:107
1336 #: rhodecode/templates/journal/journal.html:186
1296 #: rhodecode/templates/journal/journal.html:186
1337 #: rhodecode/templates/settings/repo_settings.html:31
1297 #: rhodecode/templates/settings/repo_settings.html:31
1338 #: rhodecode/templates/summary/summary.html:43
1298 #: rhodecode/templates/summary/summary.html:43
1339 #: rhodecode/templates/summary/summary.html:123
1299 #: rhodecode/templates/summary/summary.html:123
1340 #: rhodecode/templates/tags/tags.html:36
1300 #: rhodecode/templates/tags/tags.html:36
1341 #: rhodecode/templates/tags/tags_data.html:6
1301 #: rhodecode/templates/tags/tags_data.html:6
1342 msgid "Name"
1302 msgid "Name"
1343 msgstr "名称"
1303 msgstr "名称"
1344
1304
1345 #: rhodecode/templates/index_base.html:72
1305 #: rhodecode/templates/index_base.html:72
1346 msgid "Last change"
1306 msgid "Last change"
1347 msgstr "最后修改"
1307 msgstr "最后修改"
1348
1308
1349 #: rhodecode/templates/index_base.html:73
1309 #: rhodecode/templates/index_base.html:73
1350 #: rhodecode/templates/index_base.html:171
1310 #: rhodecode/templates/index_base.html:171
1351 #: rhodecode/templates/admin/users/user_edit_my_account.html:159
1311 #: rhodecode/templates/admin/users/user_edit_my_account.html:159
1352 #: rhodecode/templates/journal/journal.html:188
1312 #: rhodecode/templates/journal/journal.html:188
1353 msgid "Tip"
1313 msgid "Tip"
1354 msgstr ""
1314 msgstr "Tip"
1355
1315
1356 #: rhodecode/templates/index_base.html:74
1316 #: rhodecode/templates/index_base.html:74
1357 #: rhodecode/templates/index_base.html:173
1317 #: rhodecode/templates/index_base.html:173
1358 #: rhodecode/templates/admin/repos/repo_edit.html:121
1318 #: rhodecode/templates/admin/repos/repo_edit.html:121
1359 #: rhodecode/templates/admin/repos/repos.html:73
1319 #: rhodecode/templates/admin/repos/repos.html:73
1360 msgid "Owner"
1320 msgid "Owner"
1361 msgstr "所有者"
1321 msgstr "所有者"
1362
1322
1363 #: rhodecode/templates/index_base.html:75
1323 #: rhodecode/templates/index_base.html:75
1364 #: rhodecode/templates/summary/summary.html:48
1324 #: rhodecode/templates/summary/summary.html:48
1365 #: rhodecode/templates/summary/summary.html:51
1325 #: rhodecode/templates/summary/summary.html:51
1366 msgid "RSS"
1326 msgid "RSS"
1367 msgstr ""
1327 msgstr "RSS"
1368
1328
1369 #: rhodecode/templates/index_base.html:76
1329 #: rhodecode/templates/index_base.html:76
1370 msgid "Atom"
1330 msgid "Atom"
1371 msgstr ""
1331 msgstr "Atom"
1372
1332
1373 #: rhodecode/templates/index_base.html:110
1333 #: rhodecode/templates/index_base.html:110
1374 #: rhodecode/templates/index_base.html:112
1334 #: rhodecode/templates/index_base.html:112
1375 #, python-format
1335 #, python-format
1376 msgid "Subscribe to %s rss feed"
1336 msgid "Subscribe to %s rss feed"
1377 msgstr "订阅 rss %s"
1337 msgstr "订阅 %s 的 RSS"
1378
1338
1379 #: rhodecode/templates/index_base.html:117
1339 #: rhodecode/templates/index_base.html:117
1380 #: rhodecode/templates/index_base.html:119
1340 #: rhodecode/templates/index_base.html:119
1381 #, python-format
1341 #, python-format
1382 msgid "Subscribe to %s atom feed"
1342 msgid "Subscribe to %s atom feed"
1383 msgstr "订阅 atom %s"
1343 msgstr "订阅 %s 的 Atom"
1384
1344
1385 #: rhodecode/templates/index_base.html:140
1345 #: rhodecode/templates/index_base.html:140
1386 #, fuzzy
1387 msgid "Group Name"
1346 msgid "Group Name"
1388 msgstr "组名"
1347 msgstr "组名"
1389
1348
1390 #: rhodecode/templates/index_base.html:158
1349 #: rhodecode/templates/index_base.html:158
1391 #: rhodecode/templates/index_base.html:198
1350 #: rhodecode/templates/index_base.html:198
1392 #: rhodecode/templates/admin/repos/repos.html:94
1351 #: rhodecode/templates/admin/repos/repos.html:94
1393 #: rhodecode/templates/admin/users/user_edit_my_account.html:179
1352 #: rhodecode/templates/admin/users/user_edit_my_account.html:179
1394 #: rhodecode/templates/admin/users/users.html:107
1353 #: rhodecode/templates/admin/users/users.html:107
1395 #: rhodecode/templates/bookmarks/bookmarks.html:60
1354 #: rhodecode/templates/bookmarks/bookmarks.html:60
1396 #: rhodecode/templates/branches/branches.html:77
1355 #: rhodecode/templates/branches/branches.html:77
1397 #: rhodecode/templates/journal/journal.html:211
1356 #: rhodecode/templates/journal/journal.html:211
1398 #: rhodecode/templates/tags/tags.html:60
1357 #: rhodecode/templates/tags/tags.html:60
1399 msgid "Click to sort ascending"
1358 msgid "Click to sort ascending"
1400 msgstr ""
1359 msgstr "点击以升序排列"
1401
1360
1402 #: rhodecode/templates/index_base.html:159
1361 #: rhodecode/templates/index_base.html:159
1403 #: rhodecode/templates/index_base.html:199
1362 #: rhodecode/templates/index_base.html:199
1404 #: rhodecode/templates/admin/repos/repos.html:95
1363 #: rhodecode/templates/admin/repos/repos.html:95
1405 #: rhodecode/templates/admin/users/user_edit_my_account.html:180
1364 #: rhodecode/templates/admin/users/user_edit_my_account.html:180
1406 #: rhodecode/templates/admin/users/users.html:108
1365 #: rhodecode/templates/admin/users/users.html:108
1407 #: rhodecode/templates/bookmarks/bookmarks.html:61
1366 #: rhodecode/templates/bookmarks/bookmarks.html:61
1408 #: rhodecode/templates/branches/branches.html:78
1367 #: rhodecode/templates/branches/branches.html:78
1409 #: rhodecode/templates/journal/journal.html:212
1368 #: rhodecode/templates/journal/journal.html:212
1410 #: rhodecode/templates/tags/tags.html:61
1369 #: rhodecode/templates/tags/tags.html:61
1411 msgid "Click to sort descending"
1370 msgid "Click to sort descending"
1412 msgstr ""
1371 msgstr "点击以降序排列"
1413
1372
1414 #: rhodecode/templates/index_base.html:169
1373 #: rhodecode/templates/index_base.html:169
1415 #, fuzzy
1416 msgid "Last Change"
1374 msgid "Last Change"
1417 msgstr "最后修改"
1375 msgstr "最后修改"
1418
1376
1419 #: rhodecode/templates/index_base.html:200
1377 #: rhodecode/templates/index_base.html:200
1420 #: rhodecode/templates/admin/repos/repos.html:96
1378 #: rhodecode/templates/admin/repos/repos.html:96
1421 #: rhodecode/templates/admin/users/user_edit_my_account.html:181
1379 #: rhodecode/templates/admin/users/user_edit_my_account.html:181
1422 #: rhodecode/templates/admin/users/users.html:109
1380 #: rhodecode/templates/admin/users/users.html:109
1423 #: rhodecode/templates/bookmarks/bookmarks.html:62
1381 #: rhodecode/templates/bookmarks/bookmarks.html:62
1424 #: rhodecode/templates/branches/branches.html:79
1382 #: rhodecode/templates/branches/branches.html:79
1425 #: rhodecode/templates/journal/journal.html:213
1383 #: rhodecode/templates/journal/journal.html:213
1426 #: rhodecode/templates/tags/tags.html:62
1384 #: rhodecode/templates/tags/tags.html:62
1427 msgid "No records found."
1385 msgid "No records found."
1428 msgstr ""
1386 msgstr "没有找到记录"
1429
1387
1430 #: rhodecode/templates/index_base.html:201
1388 #: rhodecode/templates/index_base.html:201
1431 #: rhodecode/templates/admin/repos/repos.html:97
1389 #: rhodecode/templates/admin/repos/repos.html:97
1432 #: rhodecode/templates/admin/users/user_edit_my_account.html:182
1390 #: rhodecode/templates/admin/users/user_edit_my_account.html:182
1433 #: rhodecode/templates/admin/users/users.html:110
1391 #: rhodecode/templates/admin/users/users.html:110
1434 #: rhodecode/templates/bookmarks/bookmarks.html:63
1392 #: rhodecode/templates/bookmarks/bookmarks.html:63
1435 #: rhodecode/templates/branches/branches.html:80
1393 #: rhodecode/templates/branches/branches.html:80
1436 #: rhodecode/templates/journal/journal.html:214
1394 #: rhodecode/templates/journal/journal.html:214
1437 #: rhodecode/templates/tags/tags.html:63
1395 #: rhodecode/templates/tags/tags.html:63
1438 msgid "Data error."
1396 msgid "Data error."
1439 msgstr ""
1397 msgstr "数据错误"
1440
1398
1441 #: rhodecode/templates/index_base.html:202
1399 #: rhodecode/templates/index_base.html:202
1442 #: rhodecode/templates/admin/repos/repos.html:98
1400 #: rhodecode/templates/admin/repos/repos.html:98
1443 #: rhodecode/templates/admin/users/user_edit_my_account.html:183
1401 #: rhodecode/templates/admin/users/user_edit_my_account.html:183
1444 #: rhodecode/templates/admin/users/users.html:111
1402 #: rhodecode/templates/admin/users/users.html:111
1445 #: rhodecode/templates/bookmarks/bookmarks.html:64
1403 #: rhodecode/templates/bookmarks/bookmarks.html:64
1446 #: rhodecode/templates/branches/branches.html:81
1404 #: rhodecode/templates/branches/branches.html:81
1447 #: rhodecode/templates/journal/journal.html:215
1405 #: rhodecode/templates/journal/journal.html:215
1448 #: rhodecode/templates/tags/tags.html:64
1406 #: rhodecode/templates/tags/tags.html:64
1449 #, fuzzy
1450 msgid "Loading..."
1407 msgid "Loading..."
1451 msgstr "加载文件列表..."
1408 msgstr "载入中..."
1452
1409
1453 #: rhodecode/templates/login.html:5 rhodecode/templates/login.html:54
1410 #: rhodecode/templates/login.html:5 rhodecode/templates/login.html:54
1454 msgid "Sign In"
1411 msgid "Sign In"
1455 msgstr "登录"
1412 msgstr "登录"
1456
1413
1457 #: rhodecode/templates/login.html:21
1414 #: rhodecode/templates/login.html:21
1458 msgid "Sign In to"
1415 msgid "Sign In to"
1459 msgstr "登录到"
1416 msgstr "登录到"
1460
1417
1461 #: rhodecode/templates/login.html:31 rhodecode/templates/register.html:20
1418 #: rhodecode/templates/login.html:31 rhodecode/templates/register.html:20
1462 #: rhodecode/templates/admin/admin_log.html:5
1419 #: rhodecode/templates/admin/admin_log.html:5
1463 #: rhodecode/templates/admin/users/user_add.html:32
1420 #: rhodecode/templates/admin/users/user_add.html:32
1464 #: rhodecode/templates/admin/users/user_edit.html:50
1421 #: rhodecode/templates/admin/users/user_edit.html:50
1465 #: rhodecode/templates/admin/users/user_edit_my_account_form.html:26
1422 #: rhodecode/templates/admin/users/user_edit_my_account_form.html:26
1466 #: rhodecode/templates/base/base.html:83
1423 #: rhodecode/templates/base/base.html:83
1467 #: rhodecode/templates/summary/summary.html:122
1424 #: rhodecode/templates/summary/summary.html:122
1468 msgid "Username"
1425 msgid "Username"
1469 msgstr "帐号"
1426 msgstr "帐号"
1470
1427
1471 #: rhodecode/templates/login.html:40 rhodecode/templates/register.html:29
1428 #: rhodecode/templates/login.html:40 rhodecode/templates/register.html:29
1472 #: rhodecode/templates/admin/ldap/ldap.html:46
1429 #: rhodecode/templates/admin/ldap/ldap.html:46
1473 #: rhodecode/templates/admin/users/user_add.html:41
1430 #: rhodecode/templates/admin/users/user_add.html:41
1474 #: rhodecode/templates/base/base.html:92
1431 #: rhodecode/templates/base/base.html:92
1475 msgid "Password"
1432 msgid "Password"
1476 msgstr "密码"
1433 msgstr "密码"
1477
1434
1478 #: rhodecode/templates/login.html:50
1435 #: rhodecode/templates/login.html:50
1479 #, fuzzy
1480 msgid "Remember me"
1436 msgid "Remember me"
1481 msgstr "成员"
1437 msgstr "记住密码"
1482
1438
1483 #: rhodecode/templates/login.html:60
1439 #: rhodecode/templates/login.html:60
1484 msgid "Forgot your password ?"
1440 msgid "Forgot your password ?"
1485 msgstr "忘记了密码?"
1441 msgstr "忘记了密码?"
1486
1442
1487 #: rhodecode/templates/login.html:63 rhodecode/templates/base/base.html:103
1443 #: rhodecode/templates/login.html:63 rhodecode/templates/base/base.html:103
1488 msgid "Don't have an account ?"
1444 msgid "Don't have an account ?"
1489 msgstr "还没有帐号?"
1445 msgstr "还没有帐号?"
1490
1446
1491 #: rhodecode/templates/password_reset.html:5
1447 #: rhodecode/templates/password_reset.html:5
1492 msgid "Reset your password"
1448 msgid "Reset your password"
1493 msgstr "重置密码"
1449 msgstr "重置密码"
1494
1450
1495 #: rhodecode/templates/password_reset.html:11
1451 #: rhodecode/templates/password_reset.html:11
1496 msgid "Reset your password to"
1452 msgid "Reset your password to"
1497 msgstr "重置密码"
1453 msgstr "重置密码"
1498
1454
1499 #: rhodecode/templates/password_reset.html:21
1455 #: rhodecode/templates/password_reset.html:21
1500 msgid "Email address"
1456 msgid "Email address"
1501 msgstr "邮件地址"
1457 msgstr "邮件地址"
1502
1458
1503 #: rhodecode/templates/password_reset.html:30
1459 #: rhodecode/templates/password_reset.html:30
1504 msgid "Reset my password"
1460 msgid "Reset my password"
1505 msgstr "重置密码"
1461 msgstr "重置密码"
1506
1462
1507 #: rhodecode/templates/password_reset.html:31
1463 #: rhodecode/templates/password_reset.html:31
1508 msgid "Password reset link will be send to matching email address"
1464 msgid "Password reset link will be send to matching email address"
1509 msgstr "密码重置地址已经发送到邮件"
1465 msgstr "密码重置地址已经发送到邮件"
1510
1466
1511 #: rhodecode/templates/register.html:5 rhodecode/templates/register.html:74
1467 #: rhodecode/templates/register.html:5 rhodecode/templates/register.html:74
1512 msgid "Sign Up"
1468 msgid "Sign Up"
1513 msgstr "注册"
1469 msgstr "注册"
1514
1470
1515 #: rhodecode/templates/register.html:11
1471 #: rhodecode/templates/register.html:11
1516 msgid "Sign Up to"
1472 msgid "Sign Up to"
1517 msgstr "注册"
1473 msgstr "注册"
1518
1474
1519 #: rhodecode/templates/register.html:38
1475 #: rhodecode/templates/register.html:38
1520 msgid "Re-enter password"
1476 msgid "Re-enter password"
1521 msgstr "确认密码"
1477 msgstr "确认密码"
1522
1478
1523 #: rhodecode/templates/register.html:47
1479 #: rhodecode/templates/register.html:47
1524 #: rhodecode/templates/admin/users/user_add.html:59
1480 #: rhodecode/templates/admin/users/user_add.html:59
1525 #: rhodecode/templates/admin/users/user_edit.html:86
1481 #: rhodecode/templates/admin/users/user_edit.html:86
1526 #: rhodecode/templates/admin/users/user_edit_my_account_form.html:53
1482 #: rhodecode/templates/admin/users/user_edit_my_account_form.html:53
1527 msgid "First Name"
1483 msgid "First Name"
1528 msgstr "名"
1484 msgstr "名"
1529
1485
1530 #: rhodecode/templates/register.html:56
1486 #: rhodecode/templates/register.html:56
1531 #: rhodecode/templates/admin/users/user_add.html:68
1487 #: rhodecode/templates/admin/users/user_add.html:68
1532 #: rhodecode/templates/admin/users/user_edit.html:95
1488 #: rhodecode/templates/admin/users/user_edit.html:95
1533 #: rhodecode/templates/admin/users/user_edit_my_account_form.html:62
1489 #: rhodecode/templates/admin/users/user_edit_my_account_form.html:62
1534 msgid "Last Name"
1490 msgid "Last Name"
1535 msgstr "姓"
1491 msgstr "姓"
1536
1492
1537 #: rhodecode/templates/register.html:65
1493 #: rhodecode/templates/register.html:65
1538 #: rhodecode/templates/admin/users/user_add.html:77
1494 #: rhodecode/templates/admin/users/user_add.html:77
1539 #: rhodecode/templates/admin/users/user_edit.html:104
1495 #: rhodecode/templates/admin/users/user_edit.html:104
1540 #: rhodecode/templates/admin/users/user_edit_my_account_form.html:71
1496 #: rhodecode/templates/admin/users/user_edit_my_account_form.html:71
1541 #: rhodecode/templates/summary/summary.html:124
1497 #: rhodecode/templates/summary/summary.html:124
1542 msgid "Email"
1498 msgid "Email"
1543 msgstr "电子邮件"
1499 msgstr "电子邮件"
1544
1500
1545 #: rhodecode/templates/register.html:76
1501 #: rhodecode/templates/register.html:76
1546 msgid "Your account will be activated right after registration"
1502 msgid "Your account will be activated right after registration"
1547 msgstr "注册后,帐号将启用"
1503 msgstr "注册后,帐号将启用"
1548
1504
1549 #: rhodecode/templates/register.html:78
1505 #: rhodecode/templates/register.html:78
1550 msgid "Your account must wait for activation by administrator"
1506 msgid "Your account must wait for activation by administrator"
1551 msgstr "管理员审核后,你注册的帐号将被启用"
1507 msgstr "管理员审核后,你注册的帐号将被启用"
1552
1508
1553 #: rhodecode/templates/repo_switcher_list.html:11
1509 #: rhodecode/templates/repo_switcher_list.html:11
1554 #: rhodecode/templates/admin/repos/repo_add_base.html:65
1510 #: rhodecode/templates/admin/repos/repo_add_base.html:65
1555 #: rhodecode/templates/admin/repos/repo_edit.html:85
1511 #: rhodecode/templates/admin/repos/repo_edit.html:85
1556 #: rhodecode/templates/settings/repo_settings.html:76
1512 #: rhodecode/templates/settings/repo_settings.html:76
1557 msgid "Private repository"
1513 msgid "Private repository"
1558 msgstr "私有版本库"
1514 msgstr "私有版本库"
1559
1515
1560 #: rhodecode/templates/repo_switcher_list.html:16
1516 #: rhodecode/templates/repo_switcher_list.html:16
1561 msgid "Public repository"
1517 msgid "Public repository"
1562 msgstr "公共版本库"
1518 msgstr "公共版本库"
1563
1519
1564 #: rhodecode/templates/switch_to_list.html:3
1520 #: rhodecode/templates/switch_to_list.html:3
1565 #: rhodecode/templates/branches/branches.html:14
1521 #: rhodecode/templates/branches/branches.html:14
1566 msgid "branches"
1522 msgid "branches"
1567 msgstr "分支"
1523 msgstr "分支"
1568
1524
1569 #: rhodecode/templates/switch_to_list.html:10
1525 #: rhodecode/templates/switch_to_list.html:10
1570 #: rhodecode/templates/branches/branches_data.html:57
1526 #: rhodecode/templates/branches/branches_data.html:57
1571 msgid "There are no branches yet"
1527 msgid "There are no branches yet"
1572 msgstr "没有任何分支"
1528 msgstr "没有任何分支"
1573
1529
1574 #: rhodecode/templates/switch_to_list.html:15
1530 #: rhodecode/templates/switch_to_list.html:15
1575 #: rhodecode/templates/shortlog/shortlog_data.html:10
1531 #: rhodecode/templates/shortlog/shortlog_data.html:10
1576 #: rhodecode/templates/tags/tags.html:15
1532 #: rhodecode/templates/tags/tags.html:15
1577 msgid "tags"
1533 msgid "tags"
1578 msgstr "标签"
1534 msgstr "标签"
1579
1535
1580 #: rhodecode/templates/switch_to_list.html:22
1536 #: rhodecode/templates/switch_to_list.html:22
1581 #: rhodecode/templates/tags/tags_data.html:33
1537 #: rhodecode/templates/tags/tags_data.html:33
1582 msgid "There are no tags yet"
1538 msgid "There are no tags yet"
1583 msgstr "没有任何标签"
1539 msgstr "没有任何标签"
1584
1540
1585 #: rhodecode/templates/switch_to_list.html:28
1541 #: rhodecode/templates/switch_to_list.html:28
1586 #: rhodecode/templates/bookmarks/bookmarks.html:15
1542 #: rhodecode/templates/bookmarks/bookmarks.html:15
1587 msgid "bookmarks"
1543 msgid "bookmarks"
1588 msgstr ""
1544 msgstr "书签"
1589
1545
1590 #: rhodecode/templates/switch_to_list.html:35
1546 #: rhodecode/templates/switch_to_list.html:35
1591 #: rhodecode/templates/bookmarks/bookmarks_data.html:32
1547 #: rhodecode/templates/bookmarks/bookmarks_data.html:32
1592 #, fuzzy
1593 msgid "There are no bookmarks yet"
1548 msgid "There are no bookmarks yet"
1594 msgstr "尚未有任何分支"
1549 msgstr "无书签"
1595
1550
1596 #: rhodecode/templates/admin/admin.html:5
1551 #: rhodecode/templates/admin/admin.html:5
1597 #: rhodecode/templates/admin/admin.html:9
1552 #: rhodecode/templates/admin/admin.html:9
1598 msgid "Admin journal"
1553 msgid "Admin journal"
1599 msgstr "管理员日志"
1554 msgstr "管理员日志"
1600
1555
1601 #: rhodecode/templates/admin/admin_log.html:6
1556 #: rhodecode/templates/admin/admin_log.html:6
1602 #: rhodecode/templates/admin/repos/repos.html:74
1557 #: rhodecode/templates/admin/repos/repos.html:74
1603 #: rhodecode/templates/admin/users/user_edit_my_account_repos.html:8
1558 #: rhodecode/templates/admin/users/user_edit_my_account_repos.html:8
1604 #: rhodecode/templates/admin/users/user_edit_my_account_repos.html:9
1559 #: rhodecode/templates/admin/users/user_edit_my_account_repos.html:9
1605 #: rhodecode/templates/journal/journal.html:61
1560 #: rhodecode/templates/journal/journal.html:61
1606 #: rhodecode/templates/journal/journal.html:62
1561 #: rhodecode/templates/journal/journal.html:62
1607 msgid "Action"
1562 msgid "Action"
1608 msgstr "操作"
1563 msgstr "操作"
1609
1564
1610 #: rhodecode/templates/admin/admin_log.html:7
1565 #: rhodecode/templates/admin/admin_log.html:7
1611 msgid "Repository"
1566 msgid "Repository"
1612 msgstr "版本库"
1567 msgstr "版本库"
1613
1568
1614 #: rhodecode/templates/admin/admin_log.html:8
1569 #: rhodecode/templates/admin/admin_log.html:8
1615 #: rhodecode/templates/bookmarks/bookmarks.html:37
1570 #: rhodecode/templates/bookmarks/bookmarks.html:37
1616 #: rhodecode/templates/bookmarks/bookmarks_data.html:7
1571 #: rhodecode/templates/bookmarks/bookmarks_data.html:7
1617 #: rhodecode/templates/branches/branches.html:52
1572 #: rhodecode/templates/branches/branches.html:52
1618 #: rhodecode/templates/tags/tags.html:37
1573 #: rhodecode/templates/tags/tags.html:37
1619 #: rhodecode/templates/tags/tags_data.html:7
1574 #: rhodecode/templates/tags/tags_data.html:7
1620 msgid "Date"
1575 msgid "Date"
1621 msgstr "日期"
1576 msgstr "日期"
1622
1577
1623 #: rhodecode/templates/admin/admin_log.html:9
1578 #: rhodecode/templates/admin/admin_log.html:9
1624 msgid "From IP"
1579 msgid "From IP"
1625 msgstr "来源 IP"
1580 msgstr "来源 IP"
1626
1581
1627 #: rhodecode/templates/admin/admin_log.html:53
1582 #: rhodecode/templates/admin/admin_log.html:53
1628 msgid "No actions yet"
1583 msgid "No actions yet"
1629 msgstr "无操作"
1584 msgstr "无操作"
1630
1585
1631 #: rhodecode/templates/admin/ldap/ldap.html:5
1586 #: rhodecode/templates/admin/ldap/ldap.html:5
1632 msgid "LDAP administration"
1587 msgid "LDAP administration"
1633 msgstr "LDAP 管理员"
1588 msgstr "LDAP 管理员"
1634
1589
1635 #: rhodecode/templates/admin/ldap/ldap.html:11
1590 #: rhodecode/templates/admin/ldap/ldap.html:11
1636 msgid "Ldap"
1591 msgid "Ldap"
1637 msgstr ""
1592 msgstr "LDAP"
1638
1593
1639 #: rhodecode/templates/admin/ldap/ldap.html:28
1594 #: rhodecode/templates/admin/ldap/ldap.html:28
1640 msgid "Connection settings"
1595 msgid "Connection settings"
1641 msgstr "连接设置"
1596 msgstr "连接设置"
1642
1597
1643 #: rhodecode/templates/admin/ldap/ldap.html:30
1598 #: rhodecode/templates/admin/ldap/ldap.html:30
1644 msgid "Enable LDAP"
1599 msgid "Enable LDAP"
1645 msgstr "启用 LDAP"
1600 msgstr "启用 LDAP"
1646
1601
1647 #: rhodecode/templates/admin/ldap/ldap.html:34
1602 #: rhodecode/templates/admin/ldap/ldap.html:34
1648 msgid "Host"
1603 msgid "Host"
1649 msgstr "主机"
1604 msgstr "主机"
1650
1605
1651 #: rhodecode/templates/admin/ldap/ldap.html:38
1606 #: rhodecode/templates/admin/ldap/ldap.html:38
1652 msgid "Port"
1607 msgid "Port"
1653 msgstr "端口"
1608 msgstr "端口"
1654
1609
1655 #: rhodecode/templates/admin/ldap/ldap.html:42
1610 #: rhodecode/templates/admin/ldap/ldap.html:42
1656 msgid "Account"
1611 msgid "Account"
1657 msgstr "帐号"
1612 msgstr "帐号"
1658
1613
1659 #: rhodecode/templates/admin/ldap/ldap.html:50
1614 #: rhodecode/templates/admin/ldap/ldap.html:50
1660 msgid "Connection security"
1615 msgid "Connection security"
1661 msgstr "连接安全"
1616 msgstr "连接安全"
1662
1617
1663 #: rhodecode/templates/admin/ldap/ldap.html:54
1618 #: rhodecode/templates/admin/ldap/ldap.html:54
1664 msgid "Certificate Checks"
1619 msgid "Certificate Checks"
1665 msgstr "凭证确认"
1620 msgstr "凭证确认"
1666
1621
1667 #: rhodecode/templates/admin/ldap/ldap.html:57
1622 #: rhodecode/templates/admin/ldap/ldap.html:57
1668 msgid "Search settings"
1623 msgid "Search settings"
1669 msgstr "搜索设置"
1624 msgstr "搜索设置"
1670
1625
1671 #: rhodecode/templates/admin/ldap/ldap.html:59
1626 #: rhodecode/templates/admin/ldap/ldap.html:59
1672 msgid "Base DN"
1627 msgid "Base DN"
1673 msgstr ""
1628 msgstr "Base DN"
1674
1629
1675 #: rhodecode/templates/admin/ldap/ldap.html:63
1630 #: rhodecode/templates/admin/ldap/ldap.html:63
1676 msgid "LDAP Filter"
1631 msgid "LDAP Filter"
1677 msgstr ""
1632 msgstr "LDAP 过滤器"
1678
1633
1679 #: rhodecode/templates/admin/ldap/ldap.html:67
1634 #: rhodecode/templates/admin/ldap/ldap.html:67
1680 msgid "LDAP Search Scope"
1635 msgid "LDAP Search Scope"
1681 msgstr ""
1636 msgstr "LDAP 搜索范围"
1682
1637
1683 #: rhodecode/templates/admin/ldap/ldap.html:70
1638 #: rhodecode/templates/admin/ldap/ldap.html:70
1684 msgid "Attribute mappings"
1639 msgid "Attribute mappings"
1685 msgstr "属性映射"
1640 msgstr "属性映射"
1686
1641
1687 #: rhodecode/templates/admin/ldap/ldap.html:72
1642 #: rhodecode/templates/admin/ldap/ldap.html:72
1688 msgid "Login Attribute"
1643 msgid "Login Attribute"
1689 msgstr "登录属性"
1644 msgstr "登录属性"
1690
1645
1691 #: rhodecode/templates/admin/ldap/ldap.html:76
1646 #: rhodecode/templates/admin/ldap/ldap.html:76
1692 msgid "First Name Attribute"
1647 msgid "First Name Attribute"
1693 msgstr "名"
1648 msgstr "名"
1694
1649
1695 #: rhodecode/templates/admin/ldap/ldap.html:80
1650 #: rhodecode/templates/admin/ldap/ldap.html:80
1696 msgid "Last Name Attribute"
1651 msgid "Last Name Attribute"
1697 msgstr "姓"
1652 msgstr "姓"
1698
1653
1699 #: rhodecode/templates/admin/ldap/ldap.html:84
1654 #: rhodecode/templates/admin/ldap/ldap.html:84
1700 msgid "E-mail Attribute"
1655 msgid "E-mail Attribute"
1701 msgstr "电子邮件属性"
1656 msgstr "电子邮件属性"
1702
1657
1703 #: rhodecode/templates/admin/ldap/ldap.html:89
1658 #: rhodecode/templates/admin/ldap/ldap.html:89
1704 #: rhodecode/templates/admin/repos/repo_edit.html:141
1659 #: rhodecode/templates/admin/repos/repo_edit.html:141
1705 #: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:74
1660 #: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:74
1706 #: rhodecode/templates/admin/settings/hooks.html:73
1661 #: rhodecode/templates/admin/settings/hooks.html:73
1707 #: rhodecode/templates/admin/users/user_edit.html:129
1662 #: rhodecode/templates/admin/users/user_edit.html:129
1708 #: rhodecode/templates/admin/users/user_edit.html:174
1663 #: rhodecode/templates/admin/users/user_edit.html:174
1709 #: rhodecode/templates/admin/users/user_edit_my_account_form.html:79
1664 #: rhodecode/templates/admin/users/user_edit_my_account_form.html:79
1710 #: rhodecode/templates/admin/users_groups/users_group_edit.html:135
1665 #: rhodecode/templates/admin/users_groups/users_group_edit.html:135
1711 #: rhodecode/templates/settings/repo_settings.html:93
1666 #: rhodecode/templates/settings/repo_settings.html:93
1712 msgid "Save"
1667 msgid "Save"
1713 msgstr "保存"
1668 msgstr "保存"
1714
1669
1715 #: rhodecode/templates/admin/notifications/notifications.html:5
1670 #: rhodecode/templates/admin/notifications/notifications.html:5
1716 #: rhodecode/templates/admin/notifications/notifications.html:9
1671 #: rhodecode/templates/admin/notifications/notifications.html:9
1717 msgid "My Notifications"
1672 msgid "My Notifications"
1718 msgstr ""
1673 msgstr "我的通知"
1719
1674
1720 #: rhodecode/templates/admin/notifications/notifications.html:29
1675 #: rhodecode/templates/admin/notifications/notifications.html:29
1721 msgid "All"
1676 msgid "All"
1722 msgstr ""
1677 msgstr "全部"
1723
1678
1724 #: rhodecode/templates/admin/notifications/notifications.html:30
1679 #: rhodecode/templates/admin/notifications/notifications.html:30
1725 #, fuzzy
1726 msgid "Comments"
1680 msgid "Comments"
1727 msgstr "提交"
1681 msgstr "评论"
1728
1682
1729 #: rhodecode/templates/admin/notifications/notifications.html:31
1683 #: rhodecode/templates/admin/notifications/notifications.html:31
1730 #: rhodecode/templates/base/base.html:254
1684 #: rhodecode/templates/base/base.html:254
1731 #: rhodecode/templates/base/base.html:256
1685 #: rhodecode/templates/base/base.html:256
1732 msgid "Pull requests"
1686 msgid "Pull requests"
1733 msgstr ""
1687 msgstr "拉取请求"
1734
1688
1735 #: rhodecode/templates/admin/notifications/notifications.html:35
1689 #: rhodecode/templates/admin/notifications/notifications.html:35
1736 msgid "Mark all read"
1690 msgid "Mark all read"
1737 msgstr ""
1691 msgstr "全部标记为已读"
1738
1692
1739 #: rhodecode/templates/admin/notifications/notifications_data.html:39
1693 #: rhodecode/templates/admin/notifications/notifications_data.html:39
1740 #, fuzzy
1741 msgid "No notifications here yet"
1694 msgid "No notifications here yet"
1742 msgstr "尚无操作"
1695 msgstr "无通知"
1743
1696
1744 #: rhodecode/templates/admin/notifications/show_notification.html:5
1697 #: rhodecode/templates/admin/notifications/show_notification.html:5
1745 #: rhodecode/templates/admin/notifications/show_notification.html:11
1698 #: rhodecode/templates/admin/notifications/show_notification.html:11
1746 #, fuzzy
1747 msgid "Show notification"
1699 msgid "Show notification"
1748 msgstr "显示注释"
1700 msgstr "显示通知"
1749
1701
1750 #: rhodecode/templates/admin/notifications/show_notification.html:9
1702 #: rhodecode/templates/admin/notifications/show_notification.html:9
1751 #, fuzzy
1752 msgid "Notifications"
1703 msgid "Notifications"
1753 msgstr "位置"
1704 msgstr "通知"
1754
1705
1755 #: rhodecode/templates/admin/permissions/permissions.html:5
1706 #: rhodecode/templates/admin/permissions/permissions.html:5
1756 msgid "Permissions administration"
1707 msgid "Permissions administration"
1757 msgstr "权限管理"
1708 msgstr "权限管理"
1758
1709
1759 #: rhodecode/templates/admin/permissions/permissions.html:11
1710 #: rhodecode/templates/admin/permissions/permissions.html:11
1760 #: rhodecode/templates/admin/repos/repo_edit.html:134
1711 #: rhodecode/templates/admin/repos/repo_edit.html:134
1761 #: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:58
1712 #: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:58
1762 #: rhodecode/templates/admin/users/user_edit.html:139
1713 #: rhodecode/templates/admin/users/user_edit.html:139
1763 #: rhodecode/templates/admin/users_groups/users_group_edit.html:100
1714 #: rhodecode/templates/admin/users_groups/users_group_edit.html:100
1764 #: rhodecode/templates/settings/repo_settings.html:86
1715 #: rhodecode/templates/settings/repo_settings.html:86
1765 msgid "Permissions"
1716 msgid "Permissions"
1766 msgstr "权限"
1717 msgstr "权限"
1767
1718
1768 #: rhodecode/templates/admin/permissions/permissions.html:24
1719 #: rhodecode/templates/admin/permissions/permissions.html:24
1769 msgid "Default permissions"
1720 msgid "Default permissions"
1770 msgstr "默认权限"
1721 msgstr "默认权限"
1771
1722
1772 #: rhodecode/templates/admin/permissions/permissions.html:31
1723 #: rhodecode/templates/admin/permissions/permissions.html:31
1773 msgid "Anonymous access"
1724 msgid "Anonymous access"
1774 msgstr "匿名访问"
1725 msgstr "匿名访问"
1775
1726
1776 #: rhodecode/templates/admin/permissions/permissions.html:41
1727 #: rhodecode/templates/admin/permissions/permissions.html:41
1777 msgid "Repository permission"
1728 msgid "Repository permission"
1778 msgstr "版本库权限"
1729 msgstr "版本库权限"
1779
1730
1780 #: rhodecode/templates/admin/permissions/permissions.html:49
1731 #: rhodecode/templates/admin/permissions/permissions.html:49
1781 msgid ""
1732 msgid ""
1782 "All default permissions on each repository will be reset to choosen "
1733 "All default permissions on each repository will be reset to choosen "
1783 "permission, note that all custom default permission on repositories will "
1734 "permission, note that all custom default permission on repositories will be "
1784 "be lost"
1735 "lost"
1785 msgstr ""
1736 msgstr ""
1737 "所有版本库的默认权限将被重置到选择的权限,所有版本库的自定义权限将被丢弃"
1786
1738
1787 #: rhodecode/templates/admin/permissions/permissions.html:50
1739 #: rhodecode/templates/admin/permissions/permissions.html:50
1788 msgid "overwrite existing settings"
1740 msgid "overwrite existing settings"
1789 msgstr "覆盖已有设置"
1741 msgstr "覆盖已有设置"
1790
1742
1791 #: rhodecode/templates/admin/permissions/permissions.html:55
1743 #: rhodecode/templates/admin/permissions/permissions.html:55
1792 msgid "Registration"
1744 msgid "Registration"
1793 msgstr "注册"
1745 msgstr "注册"
1794
1746
1795 #: rhodecode/templates/admin/permissions/permissions.html:63
1747 #: rhodecode/templates/admin/permissions/permissions.html:63
1796 msgid "Repository creation"
1748 msgid "Repository creation"
1797 msgstr "建立版本库"
1749 msgstr "建立版本库"
1798
1750
1799 #: rhodecode/templates/admin/permissions/permissions.html:71
1751 #: rhodecode/templates/admin/permissions/permissions.html:71
1800 #, fuzzy
1801 msgid "Repository forking"
1752 msgid "Repository forking"
1802 msgstr "建立版本库"
1753 msgstr "版本库分支"
1803
1754
1804 #: rhodecode/templates/admin/permissions/permissions.html:78
1755 #: rhodecode/templates/admin/permissions/permissions.html:78
1805 #: rhodecode/templates/admin/repos/repo_edit.html:241
1756 #: rhodecode/templates/admin/repos/repo_edit.html:241
1806 msgid "set"
1757 msgid "set"
1807 msgstr "设置"
1758 msgstr "设置"
1808
1759
1809 #: rhodecode/templates/admin/repos/repo_add.html:5
1760 #: rhodecode/templates/admin/repos/repo_add.html:5
1810 #: rhodecode/templates/admin/repos/repo_add_create_repository.html:5
1761 #: rhodecode/templates/admin/repos/repo_add_create_repository.html:5
1811 msgid "Add repository"
1762 msgid "Add repository"
1812 msgstr "添加版本库"
1763 msgstr "添加版本库"
1813
1764
1814 #: rhodecode/templates/admin/repos/repo_add.html:11
1765 #: rhodecode/templates/admin/repos/repo_add.html:11
1815 #: rhodecode/templates/admin/repos/repo_edit.html:11
1766 #: rhodecode/templates/admin/repos/repo_edit.html:11
1816 #: rhodecode/templates/admin/repos_groups/repos_groups_show.html:10
1767 #: rhodecode/templates/admin/repos_groups/repos_groups_show.html:10
1817 msgid "Repositories"
1768 msgid "Repositories"
1818 msgstr "版本库"
1769 msgstr "版本库"
1819
1770
1820 #: rhodecode/templates/admin/repos/repo_add.html:13
1771 #: rhodecode/templates/admin/repos/repo_add.html:13
1821 msgid "add new"
1772 msgid "add new"
1822 msgstr "新"
1773 msgstr "新"
1823
1774
1824 #: rhodecode/templates/admin/repos/repo_add_base.html:20
1775 #: rhodecode/templates/admin/repos/repo_add_base.html:20
1825 #: rhodecode/templates/summary/summary.html:95
1776 #: rhodecode/templates/summary/summary.html:95
1826 #: rhodecode/templates/summary/summary.html:96
1777 #: rhodecode/templates/summary/summary.html:96
1827 msgid "Clone from"
1778 msgid "Clone from"
1828 msgstr "clone 自"
1779 msgstr "克隆自"
1829
1780
1830 #: rhodecode/templates/admin/repos/repo_add_base.html:24
1781 #: rhodecode/templates/admin/repos/repo_add_base.html:24
1831 #: rhodecode/templates/admin/repos/repo_edit.html:44
1782 #: rhodecode/templates/admin/repos/repo_edit.html:44
1832 #: rhodecode/templates/settings/repo_settings.html:43
1783 #: rhodecode/templates/settings/repo_settings.html:43
1833 msgid "Optional http[s] url from which repository should be cloned."
1784 msgid "Optional http[s] url from which repository should be cloned."
1834 msgstr ""
1785 msgstr "可选的,指定版本库应该从哪个 http[s] 地址克隆。"
1835
1786
1836 #: rhodecode/templates/admin/repos/repo_add_base.html:29
1787 #: rhodecode/templates/admin/repos/repo_add_base.html:29
1837 #: rhodecode/templates/admin/repos/repo_edit.html:49
1788 #: rhodecode/templates/admin/repos/repo_edit.html:49
1838 #: rhodecode/templates/admin/repos_groups/repos_groups.html:4
1789 #: rhodecode/templates/admin/repos_groups/repos_groups.html:4
1839 #: rhodecode/templates/forks/fork.html:50
1790 #: rhodecode/templates/forks/fork.html:50
1840 #: rhodecode/templates/settings/repo_settings.html:48
1791 #: rhodecode/templates/settings/repo_settings.html:48
1841 msgid "Repository group"
1792 msgid "Repository group"
1842 msgstr "版本库组"
1793 msgstr "版本库组"
1843
1794
1844 #: rhodecode/templates/admin/repos/repo_add_base.html:33
1795 #: rhodecode/templates/admin/repos/repo_add_base.html:33
1845 #: rhodecode/templates/forks/fork.html:54
1796 #: rhodecode/templates/forks/fork.html:54
1846 msgid "Optionaly select a group to put this repository into."
1797 msgid "Optionaly select a group to put this repository into."
1847 msgstr ""
1798 msgstr "可选的,选择一个组将版本库放到其中"
1848
1799
1849 #: rhodecode/templates/admin/repos/repo_add_base.html:38
1800 #: rhodecode/templates/admin/repos/repo_add_base.html:38
1850 #: rhodecode/templates/admin/repos/repo_edit.html:58
1801 #: rhodecode/templates/admin/repos/repo_edit.html:58
1851 msgid "Type"
1802 msgid "Type"
1852 msgstr "类型"
1803 msgstr "类型"
1853
1804
1854 #: rhodecode/templates/admin/repos/repo_add_base.html:42
1805 #: rhodecode/templates/admin/repos/repo_add_base.html:42
1855 #, fuzzy
1856 msgid "Type of repository to create."
1806 msgid "Type of repository to create."
1857 msgstr "建立版本库"
1807 msgstr "要创建的版本库类型"
1858
1808
1859 #: rhodecode/templates/admin/repos/repo_add_base.html:47
1809 #: rhodecode/templates/admin/repos/repo_add_base.html:47
1860 #: rhodecode/templates/admin/repos/repo_edit.html:66
1810 #: rhodecode/templates/admin/repos/repo_edit.html:66
1861 #: rhodecode/templates/forks/fork.html:41
1811 #: rhodecode/templates/forks/fork.html:41
1862 #: rhodecode/templates/settings/repo_settings.html:57
1812 #: rhodecode/templates/settings/repo_settings.html:57
1863 #, fuzzy
1864 msgid "Landing revision"
1813 msgid "Landing revision"
1865 msgstr "下一个修订"
1814 msgstr "默认修订"
1866
1815
1867 #: rhodecode/templates/admin/repos/repo_add_base.html:51
1816 #: rhodecode/templates/admin/repos/repo_add_base.html:51
1868 #: rhodecode/templates/admin/repos/repo_edit.html:70
1817 #: rhodecode/templates/admin/repos/repo_edit.html:70
1869 #: rhodecode/templates/forks/fork.html:45
1818 #: rhodecode/templates/forks/fork.html:45
1870 #: rhodecode/templates/settings/repo_settings.html:61
1819 #: rhodecode/templates/settings/repo_settings.html:61
1871 msgid "Default revision for files page, downloads, whoosh and readme"
1820 msgid "Default revision for files page, downloads, whoosh and readme"
1872 msgstr ""
1821 msgstr "文件浏览、下载、whoosh和readme的默认修订版本"
1873
1822
1874 #: rhodecode/templates/admin/repos/repo_add_base.html:60
1823 #: rhodecode/templates/admin/repos/repo_add_base.html:60
1875 #: rhodecode/templates/admin/repos/repo_edit.html:79
1824 #: rhodecode/templates/admin/repos/repo_edit.html:79
1876 #: rhodecode/templates/forks/fork.html:63
1825 #: rhodecode/templates/forks/fork.html:63
1877 #: rhodecode/templates/settings/repo_settings.html:70
1826 #: rhodecode/templates/settings/repo_settings.html:70
1878 msgid "Keep it short and to the point. Use a README file for longer descriptions."
1827 msgid ""
1879 msgstr ""
1828 "Keep it short and to the point. Use a README file for longer descriptions."
1829 msgstr "保持简短。用 README 文件来写更长的描述。"
1880
1830
1881 #: rhodecode/templates/admin/repos/repo_add_base.html:69
1831 #: rhodecode/templates/admin/repos/repo_add_base.html:69
1882 #: rhodecode/templates/admin/repos/repo_edit.html:89
1832 #: rhodecode/templates/admin/repos/repo_edit.html:89
1883 #: rhodecode/templates/forks/fork.html:72
1833 #: rhodecode/templates/forks/fork.html:72
1884 #: rhodecode/templates/settings/repo_settings.html:80
1834 #: rhodecode/templates/settings/repo_settings.html:80
1885 msgid ""
1835 msgid ""
1886 "Private repositories are only visible to people explicitly added as "
1836 "Private repositories are only visible to people explicitly added as "
1887 "collaborators."
1837 "collaborators."
1888 msgstr ""
1838 msgstr "私有版本库只对显示添加的合作者可见。"
1889
1839
1890 #: rhodecode/templates/admin/repos/repo_add_base.html:73
1840 #: rhodecode/templates/admin/repos/repo_add_base.html:73
1891 msgid "add"
1841 msgid "add"
1892 msgstr "新"
1842 msgstr "新"
1893
1843
1894 #: rhodecode/templates/admin/repos/repo_add_create_repository.html:9
1844 #: rhodecode/templates/admin/repos/repo_add_create_repository.html:9
1895 msgid "add new repository"
1845 msgid "add new repository"
1896 msgstr "新版本库"
1846 msgstr "新版本库"
1897
1847
1898 #: rhodecode/templates/admin/repos/repo_edit.html:5
1848 #: rhodecode/templates/admin/repos/repo_edit.html:5
1899 msgid "Edit repository"
1849 msgid "Edit repository"
1900 msgstr "编辑版本库"
1850 msgstr "编辑版本库"
1901
1851
1902 #: rhodecode/templates/admin/repos/repo_edit.html:13
1852 #: rhodecode/templates/admin/repos/repo_edit.html:13
1903 #: rhodecode/templates/admin/users/user_edit.html:13
1853 #: rhodecode/templates/admin/users/user_edit.html:13
1904 #: rhodecode/templates/admin/users/user_edit.html:224
1854 #: rhodecode/templates/admin/users/user_edit.html:224
1905 #: rhodecode/templates/admin/users/user_edit.html:226
1855 #: rhodecode/templates/admin/users/user_edit.html:226
1906 #: rhodecode/templates/admin/users/user_edit_my_account_repos.html:28
1856 #: rhodecode/templates/admin/users/user_edit_my_account_repos.html:28
1907 #: rhodecode/templates/admin/users_groups/users_group_edit.html:13
1857 #: rhodecode/templates/admin/users_groups/users_group_edit.html:13
1908 #: rhodecode/templates/files/files_source.html:44
1858 #: rhodecode/templates/files/files_source.html:44
1909 #: rhodecode/templates/journal/journal.html:81
1859 #: rhodecode/templates/journal/journal.html:81
1910 msgid "edit"
1860 msgid "edit"
1911 msgstr "编辑"
1861 msgstr "编辑"
1912
1862
1913 #: rhodecode/templates/admin/repos/repo_edit.html:40
1863 #: rhodecode/templates/admin/repos/repo_edit.html:40
1914 #: rhodecode/templates/settings/repo_settings.html:39
1864 #: rhodecode/templates/settings/repo_settings.html:39
1915 msgid "Clone uri"
1865 msgid "Clone uri"
1916 msgstr "clone 地址"
1866 msgstr "克隆地址"
1917
1867
1918 #: rhodecode/templates/admin/repos/repo_edit.html:53
1868 #: rhodecode/templates/admin/repos/repo_edit.html:53
1919 #: rhodecode/templates/settings/repo_settings.html:52
1869 #: rhodecode/templates/settings/repo_settings.html:52
1920 msgid "Optional select a group to put this repository into."
1870 msgid "Optional select a group to put this repository into."
1921 msgstr ""
1871 msgstr "可选的,选择一个组将版本库放到其中"
1922
1872
1923 #: rhodecode/templates/admin/repos/repo_edit.html:94
1873 #: rhodecode/templates/admin/repos/repo_edit.html:94
1924 msgid "Enable statistics"
1874 msgid "Enable statistics"
1925 msgstr "启用统计"
1875 msgstr "启用统计"
1926
1876
1927 #: rhodecode/templates/admin/repos/repo_edit.html:98
1877 #: rhodecode/templates/admin/repos/repo_edit.html:98
1928 msgid "Enable statistics window on summary page."
1878 msgid "Enable statistics window on summary page."
1929 msgstr ""
1879 msgstr "启用概况页的统计窗口"
1930
1880
1931 #: rhodecode/templates/admin/repos/repo_edit.html:103
1881 #: rhodecode/templates/admin/repos/repo_edit.html:103
1932 msgid "Enable downloads"
1882 msgid "Enable downloads"
1933 msgstr "启用下载"
1883 msgstr "启用下载"
1934
1884
1935 #: rhodecode/templates/admin/repos/repo_edit.html:107
1885 #: rhodecode/templates/admin/repos/repo_edit.html:107
1936 msgid "Enable download menu on summary page."
1886 msgid "Enable download menu on summary page."
1937 msgstr ""
1887 msgstr "启用概况页的下载菜单"
1938
1888
1939 #: rhodecode/templates/admin/repos/repo_edit.html:112
1889 #: rhodecode/templates/admin/repos/repo_edit.html:112
1940 #: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:66
1890 #: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:66
1941 #, fuzzy
1942 msgid "Enable locking"
1891 msgid "Enable locking"
1943 msgstr "启用"
1892 msgstr "启用锁定"
1944
1893
1945 #: rhodecode/templates/admin/repos/repo_edit.html:116
1894 #: rhodecode/templates/admin/repos/repo_edit.html:116
1946 msgid "Enable lock-by-pulling on repository."
1895 msgid "Enable lock-by-pulling on repository."
1947 msgstr ""
1896 msgstr "启用版本库的拉取锁定"
1948
1897
1949 #: rhodecode/templates/admin/repos/repo_edit.html:126
1898 #: rhodecode/templates/admin/repos/repo_edit.html:126
1950 #, fuzzy
1951 msgid "Change owner of this repository."
1899 msgid "Change owner of this repository."
1952 msgstr "%s 库的修改"
1900 msgstr "修改这个版本库的所有者"
1953
1901
1954 #: rhodecode/templates/admin/repos/repo_edit.html:142
1902 #: rhodecode/templates/admin/repos/repo_edit.html:142
1955 #: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:75
1903 #: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:75
1956 #: rhodecode/templates/admin/settings/settings.html:113
1904 #: rhodecode/templates/admin/settings/settings.html:113
1957 #: rhodecode/templates/admin/settings/settings.html:168
1905 #: rhodecode/templates/admin/settings/settings.html:168
1958 #: rhodecode/templates/admin/settings/settings.html:258
1906 #: rhodecode/templates/admin/settings/settings.html:258
1959 #: rhodecode/templates/admin/users/user_edit.html:130
1907 #: rhodecode/templates/admin/users/user_edit.html:130
1960 #: rhodecode/templates/admin/users/user_edit.html:175
1908 #: rhodecode/templates/admin/users/user_edit.html:175
1961 #: rhodecode/templates/admin/users/user_edit.html:278
1909 #: rhodecode/templates/admin/users/user_edit.html:278
1962 #: rhodecode/templates/admin/users/user_edit_my_account_form.html:80
1910 #: rhodecode/templates/admin/users/user_edit_my_account_form.html:80
1963 #: rhodecode/templates/admin/users_groups/users_group_edit.html:136
1911 #: rhodecode/templates/admin/users_groups/users_group_edit.html:136
1964 #: rhodecode/templates/files/files_add.html:82
1912 #: rhodecode/templates/files/files_add.html:82
1965 #: rhodecode/templates/files/files_edit.html:68
1913 #: rhodecode/templates/files/files_edit.html:68
1966 #: rhodecode/templates/pullrequests/pullrequest.html:124
1914 #: rhodecode/templates/pullrequests/pullrequest.html:124
1967 #: rhodecode/templates/settings/repo_settings.html:94
1915 #: rhodecode/templates/settings/repo_settings.html:94
1968 msgid "Reset"
1916 msgid "Reset"
1969 msgstr "重置"
1917 msgstr "重置"
1970
1918
1971 #: rhodecode/templates/admin/repos/repo_edit.html:152
1919 #: rhodecode/templates/admin/repos/repo_edit.html:152
1972 msgid "Administration"
1920 msgid "Administration"
1973 msgstr "管理"
1921 msgstr "管理"
1974
1922
1975 #: rhodecode/templates/admin/repos/repo_edit.html:155
1923 #: rhodecode/templates/admin/repos/repo_edit.html:155
1976 msgid "Statistics"
1924 msgid "Statistics"
1977 msgstr "统计"
1925 msgstr "统计"
1978
1926
1979 #: rhodecode/templates/admin/repos/repo_edit.html:159
1927 #: rhodecode/templates/admin/repos/repo_edit.html:159
1980 msgid "Reset current statistics"
1928 msgid "Reset current statistics"
1981 msgstr "重置统计"
1929 msgstr "重置统计"
1982
1930
1983 #: rhodecode/templates/admin/repos/repo_edit.html:159
1931 #: rhodecode/templates/admin/repos/repo_edit.html:159
1984 msgid "Confirm to remove current statistics"
1932 msgid "Confirm to remove current statistics"
1985 msgstr "确认移除当前统计"
1933 msgstr "确认移除当前统计"
1986
1934
1987 #: rhodecode/templates/admin/repos/repo_edit.html:162
1935 #: rhodecode/templates/admin/repos/repo_edit.html:162
1988 msgid "Fetched to rev"
1936 msgid "Fetched to rev"
1989 msgstr ""
1937 msgstr "获取到修订"
1990
1938
1991 #: rhodecode/templates/admin/repos/repo_edit.html:163
1939 #: rhodecode/templates/admin/repos/repo_edit.html:163
1992 msgid "Stats gathered"
1940 msgid "Stats gathered"
1993 msgstr ""
1941 msgstr "已收集的统计"
1994
1942
1995 #: rhodecode/templates/admin/repos/repo_edit.html:171
1943 #: rhodecode/templates/admin/repos/repo_edit.html:171
1996 msgid "Remote"
1944 msgid "Remote"
1997 msgstr "远程"
1945 msgstr "远程"
1998
1946
1999 #: rhodecode/templates/admin/repos/repo_edit.html:175
1947 #: rhodecode/templates/admin/repos/repo_edit.html:175
2000 msgid "Pull changes from remote location"
1948 msgid "Pull changes from remote location"
2001 msgstr ""
1949 msgstr "从远程路径拉取修订集"
2002
1950
2003 #: rhodecode/templates/admin/repos/repo_edit.html:175
1951 #: rhodecode/templates/admin/repos/repo_edit.html:175
2004 msgid "Confirm to pull changes from remote side"
1952 msgid "Confirm to pull changes from remote side"
2005 msgstr ""
1953 msgstr "确认从远程拉取修订集"
2006
1954
2007 #: rhodecode/templates/admin/repos/repo_edit.html:186
1955 #: rhodecode/templates/admin/repos/repo_edit.html:186
2008 msgid "Cache"
1956 msgid "Cache"
2009 msgstr "缓存"
1957 msgstr "缓存"
2010
1958
2011 #: rhodecode/templates/admin/repos/repo_edit.html:190
1959 #: rhodecode/templates/admin/repos/repo_edit.html:190
2012 msgid "Invalidate repository cache"
1960 msgid "Invalidate repository cache"
2013 msgstr ""
1961 msgstr "清除版本库缓存"
2014
1962
2015 #: rhodecode/templates/admin/repos/repo_edit.html:190
1963 #: rhodecode/templates/admin/repos/repo_edit.html:190
2016 msgid "Confirm to invalidate repository cache"
1964 msgid "Confirm to invalidate repository cache"
2017 msgstr "确认清除版本库缓存"
1965 msgstr "确认清除版本库缓存"
2018
1966
2019 #: rhodecode/templates/admin/repos/repo_edit.html:195
1967 #: rhodecode/templates/admin/repos/repo_edit.html:195
2020 #: rhodecode/templates/base/base.html:318
1968 #: rhodecode/templates/base/base.html:318
2021 #: rhodecode/templates/base/base.html:320
1969 #: rhodecode/templates/base/base.html:320
2022 #: rhodecode/templates/base/base.html:322
1970 #: rhodecode/templates/base/base.html:322
2023 msgid "Public journal"
1971 msgid "Public journal"
2024 msgstr "公共日志"
1972 msgstr "公共日志"
2025
1973
2026 #: rhodecode/templates/admin/repos/repo_edit.html:201
1974 #: rhodecode/templates/admin/repos/repo_edit.html:201
2027 msgid "Remove from public journal"
1975 msgid "Remove from public journal"
2028 msgstr "从公共日志删除"
1976 msgstr "从公共日志删除"
2029
1977
2030 #: rhodecode/templates/admin/repos/repo_edit.html:203
1978 #: rhodecode/templates/admin/repos/repo_edit.html:203
2031 msgid "Add to public journal"
1979 msgid "Add to public journal"
2032 msgstr "添加到公共日志"
1980 msgstr "添加到公共日志"
2033
1981
2034 #: rhodecode/templates/admin/repos/repo_edit.html:208
1982 #: rhodecode/templates/admin/repos/repo_edit.html:208
2035 msgid ""
1983 msgid ""
2036 "All actions made on this repository will be accessible to everyone in "
1984 "All actions made on this repository will be accessible to everyone in public "
2037 "public journal"
1985 "journal"
2038 msgstr ""
1986 msgstr "任何人都可以在公共日志上看到这个版本库上的所有动作"
2039
1987
2040 #: rhodecode/templates/admin/repos/repo_edit.html:215
1988 #: rhodecode/templates/admin/repos/repo_edit.html:215
2041 #, fuzzy
2042 msgid "Locking"
1989 msgid "Locking"
2043 msgstr "锁"
1990 msgstr "锁"
2044
1991
2045 #: rhodecode/templates/admin/repos/repo_edit.html:220
1992 #: rhodecode/templates/admin/repos/repo_edit.html:220
2046 msgid "Unlock locked repo"
1993 msgid "Unlock locked repo"
2047 msgstr ""
1994 msgstr "解锁版本库"
2048
1995
2049 #: rhodecode/templates/admin/repos/repo_edit.html:220
1996 #: rhodecode/templates/admin/repos/repo_edit.html:220
2050 #, fuzzy
2051 msgid "Confirm to unlock repository"
1997 msgid "Confirm to unlock repository"
2052 msgstr "确认删除版本库"
1998 msgstr "确认解锁版本库"
2053
1999
2054 #: rhodecode/templates/admin/repos/repo_edit.html:223
2000 #: rhodecode/templates/admin/repos/repo_edit.html:223
2055 msgid "lock repo"
2001 msgid "lock repo"
2056 msgstr ""
2002 msgstr "锁定版本库"
2057
2003
2058 #: rhodecode/templates/admin/repos/repo_edit.html:223
2004 #: rhodecode/templates/admin/repos/repo_edit.html:223
2059 #, fuzzy
2060 msgid "Confirm to lock repository"
2005 msgid "Confirm to lock repository"
2061 msgstr "确认删除版本库"
2006 msgstr "确认锁定版本库"
2062
2007
2063 #: rhodecode/templates/admin/repos/repo_edit.html:224
2008 #: rhodecode/templates/admin/repos/repo_edit.html:224
2064 #, fuzzy
2065 msgid "Repository is not locked"
2009 msgid "Repository is not locked"
2066 msgstr "版本库"
2010 msgstr "版本库未锁定"
2067
2011
2068 #: rhodecode/templates/admin/repos/repo_edit.html:229
2012 #: rhodecode/templates/admin/repos/repo_edit.html:229
2069 msgid "Force locking on repository. Works only when anonymous access is disabled"
2013 msgid ""
2070 msgstr ""
2014 "Force locking on repository. Works only when anonymous access is disabled"
2015 msgstr "强制锁定版本库。只有在禁止匿名访问时候才有效"
2071
2016
2072 #: rhodecode/templates/admin/repos/repo_edit.html:236
2017 #: rhodecode/templates/admin/repos/repo_edit.html:236
2073 msgid "Set as fork of"
2018 msgid "Set as fork of"
2074 msgstr ""
2019 msgstr "设置 fork 自"
2075
2020
2076 #: rhodecode/templates/admin/repos/repo_edit.html:245
2021 #: rhodecode/templates/admin/repos/repo_edit.html:245
2077 msgid "Manually set this repository as a fork of another from the list"
2022 msgid "Manually set this repository as a fork of another from the list"
2078 msgstr ""
2023 msgstr "从列表中手动设置这个版本库 fork 自另一版本库"
2079
2024
2080 #: rhodecode/templates/admin/repos/repo_edit.html:251
2025 #: rhodecode/templates/admin/repos/repo_edit.html:251
2081 #: rhodecode/templates/changeset/changeset_file_comment.html:26
2026 #: rhodecode/templates/changeset/changeset_file_comment.html:26
2082 msgid "Delete"
2027 msgid "Delete"
2083 msgstr "删除"
2028 msgstr "删除"
2084
2029
2085 #: rhodecode/templates/admin/repos/repo_edit.html:255
2030 #: rhodecode/templates/admin/repos/repo_edit.html:255
2086 msgid "Remove this repository"
2031 msgid "Remove this repository"
2087 msgstr "删除版本库"
2032 msgstr "删除版本库"
2088
2033
2089 #: rhodecode/templates/admin/repos/repo_edit.html:255
2034 #: rhodecode/templates/admin/repos/repo_edit.html:255
2090 #: rhodecode/templates/journal/journal.html:84
2035 #: rhodecode/templates/journal/journal.html:84
2091 msgid "Confirm to delete this repository"
2036 msgid "Confirm to delete this repository"
2092 msgstr "确认删除版本库"
2037 msgstr "确认删除版本库"
2093
2038
2094 #: rhodecode/templates/admin/repos/repo_edit.html:259
2039 #: rhodecode/templates/admin/repos/repo_edit.html:259
2095 msgid ""
2040 msgid ""
2096 "This repository will be renamed in a special way in order to be "
2041 "This repository will be renamed in a special way in order to be unaccesible "
2097 "unaccesible for RhodeCode and VCS systems.\n"
2042 "for RhodeCode and VCS systems.\n"
2098 " If you need fully delete it from filesystem "
2043 " If you need fully delete it from filesystem please "
2099 "please do it manually"
2044 "do it manually"
2100 msgstr ""
2045 msgstr ""
2046 "这个版本库将以特殊的方式重命名这样 RhodeCode 和版本控制系统将不能访问它。\n"
2047 " 如果需要从文件系统完全删除,你需要手动操作"
2101
2048
2102 #: rhodecode/templates/admin/repos/repo_edit_perms.html:3
2049 #: rhodecode/templates/admin/repos/repo_edit_perms.html:3
2103 #: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:3
2050 #: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:3
2104 msgid "none"
2051 msgid "none"
2105 msgstr "无"
2052 msgstr "无"
2106
2053
2107 #: rhodecode/templates/admin/repos/repo_edit_perms.html:4
2054 #: rhodecode/templates/admin/repos/repo_edit_perms.html:4
2108 #: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:4
2055 #: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:4
2109 msgid "read"
2056 msgid "read"
2110 msgstr "读"
2057 msgstr "读"
2111
2058
2112 #: rhodecode/templates/admin/repos/repo_edit_perms.html:5
2059 #: rhodecode/templates/admin/repos/repo_edit_perms.html:5
2113 #: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:5
2060 #: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:5
2114 msgid "write"
2061 msgid "write"
2115 msgstr "写"
2062 msgstr "写"
2116
2063
2117 #: rhodecode/templates/admin/repos/repo_edit_perms.html:6
2064 #: rhodecode/templates/admin/repos/repo_edit_perms.html:6
2118 #: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:6
2065 #: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:6
2119 #: rhodecode/templates/admin/users/users.html:85
2066 #: rhodecode/templates/admin/users/users.html:85
2120 #: rhodecode/templates/base/base.html:217
2067 #: rhodecode/templates/base/base.html:217
2121 msgid "admin"
2068 msgid "admin"
2122 msgstr "管理员"
2069 msgstr "管理员"
2123
2070
2124 #: rhodecode/templates/admin/repos/repo_edit_perms.html:7
2071 #: rhodecode/templates/admin/repos/repo_edit_perms.html:7
2125 #: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:7
2072 #: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:7
2126 msgid "member"
2073 msgid "member"
2127 msgstr "成员"
2074 msgstr "成员"
2128
2075
2129 #: rhodecode/templates/admin/repos/repo_edit_perms.html:16
2076 #: rhodecode/templates/admin/repos/repo_edit_perms.html:16
2130 #: rhodecode/templates/data_table/_dt_elements.html:67
2077 #: rhodecode/templates/data_table/_dt_elements.html:67
2131 #: rhodecode/templates/journal/journal.html:132
2078 #: rhodecode/templates/journal/journal.html:132
2132 #: rhodecode/templates/summary/summary.html:76
2079 #: rhodecode/templates/summary/summary.html:76
2133 msgid "private repository"
2080 msgid "private repository"
2134 msgstr "私有版本库"
2081 msgstr "私有版本库"
2135
2082
2136 #: rhodecode/templates/admin/repos/repo_edit_perms.html:19
2083 #: rhodecode/templates/admin/repos/repo_edit_perms.html:19
2137 #: rhodecode/templates/admin/repos/repo_edit_perms.html:28
2084 #: rhodecode/templates/admin/repos/repo_edit_perms.html:28
2138 #: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:18
2085 #: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:18
2139 #, fuzzy
2140 msgid "default"
2086 msgid "default"
2141 msgstr "删除"
2087 msgstr "默认"
2142
2088
2143 #: rhodecode/templates/admin/repos/repo_edit_perms.html:33
2089 #: rhodecode/templates/admin/repos/repo_edit_perms.html:33
2144 #: rhodecode/templates/admin/repos/repo_edit_perms.html:58
2090 #: rhodecode/templates/admin/repos/repo_edit_perms.html:58
2145 #: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:23
2091 #: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:23
2146 #: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:42
2092 #: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:42
2147 msgid "revoke"
2093 msgid "revoke"
2148 msgstr ""
2094 msgstr "移除"
2149
2095
2150 #: rhodecode/templates/admin/repos/repo_edit_perms.html:83
2096 #: rhodecode/templates/admin/repos/repo_edit_perms.html:83
2151 #: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:67
2097 #: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:67
2152 msgid "Add another member"
2098 msgid "Add another member"
2153 msgstr "添加成员"
2099 msgstr "添加成员"
2154
2100
2155 #: rhodecode/templates/admin/repos/repo_edit_perms.html:97
2101 #: rhodecode/templates/admin/repos/repo_edit_perms.html:97
2156 #: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:81
2102 #: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:81
2157 msgid "Failed to remove user"
2103 msgid "Failed to remove user"
2158 msgstr "删除用户失败"
2104 msgstr "删除用户失败"
2159
2105
2160 #: rhodecode/templates/admin/repos/repo_edit_perms.html:112
2106 #: rhodecode/templates/admin/repos/repo_edit_perms.html:112
2161 #: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:96
2107 #: rhodecode/templates/admin/repos_groups/repos_group_edit_perms.html:96
2162 msgid "Failed to remove users group"
2108 msgid "Failed to remove users group"
2163 msgstr "删除用户组失败"
2109 msgstr "删除用户组失败"
2164
2110
2165 #: rhodecode/templates/admin/repos/repos.html:5
2111 #: rhodecode/templates/admin/repos/repos.html:5
2166 msgid "Repositories administration"
2112 msgid "Repositories administration"
2167 msgstr "版本库管理员"
2113 msgstr "版本库管理员"
2168
2114
2169 #: rhodecode/templates/admin/repos_groups/repos_groups.html:8
2115 #: rhodecode/templates/admin/repos_groups/repos_groups.html:8
2170 msgid "Groups"
2116 msgid "Groups"
2171 msgstr "组"
2117 msgstr "组"
2172
2118
2173 #: rhodecode/templates/admin/repos_groups/repos_groups.html:12
2119 #: rhodecode/templates/admin/repos_groups/repos_groups.html:12
2174 msgid "with"
2120 msgid "with"
2175 msgstr ""
2121 msgstr ""
2176
2122
2177 #: rhodecode/templates/admin/repos_groups/repos_groups_add.html:5
2123 #: rhodecode/templates/admin/repos_groups/repos_groups_add.html:5
2178 msgid "Add repos group"
2124 msgid "Add repos group"
2179 msgstr "添加版本库组"
2125 msgstr "添加版本库组"
2180
2126
2181 #: rhodecode/templates/admin/repos_groups/repos_groups_add.html:10
2127 #: rhodecode/templates/admin/repos_groups/repos_groups_add.html:10
2182 #: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:10
2128 #: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:10
2183 msgid "Repos groups"
2129 msgid "Repos groups"
2184 msgstr "版本库组"
2130 msgstr "版本库组"
2185
2131
2186 #: rhodecode/templates/admin/repos_groups/repos_groups_add.html:12
2132 #: rhodecode/templates/admin/repos_groups/repos_groups_add.html:12
2187 msgid "add new repos group"
2133 msgid "add new repos group"
2188 msgstr "添加新版本库组"
2134 msgstr "添加新版本库组"
2189
2135
2190 #: rhodecode/templates/admin/repos_groups/repos_groups_add.html:50
2136 #: rhodecode/templates/admin/repos_groups/repos_groups_add.html:50
2191 #: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:50
2137 #: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:50
2192 msgid "Group parent"
2138 msgid "Group parent"
2193 msgstr "上级组"
2139 msgstr "上级组"
2194
2140
2195 #: rhodecode/templates/admin/repos_groups/repos_groups_add.html:58
2141 #: rhodecode/templates/admin/repos_groups/repos_groups_add.html:58
2196 #: rhodecode/templates/admin/users/user_add.html:94
2142 #: rhodecode/templates/admin/users/user_add.html:94
2197 #: rhodecode/templates/admin/users_groups/users_group_add.html:49
2143 #: rhodecode/templates/admin/users_groups/users_group_add.html:49
2198 #: rhodecode/templates/admin/users_groups/users_group_edit.html:90
2144 #: rhodecode/templates/admin/users_groups/users_group_edit.html:90
2199 #: rhodecode/templates/pullrequests/pullrequest_show.html:113
2145 #: rhodecode/templates/pullrequests/pullrequest_show.html:113
2200 msgid "save"
2146 msgid "save"
2201 msgstr "保存"
2147 msgstr "保存"
2202
2148
2203 #: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:5
2149 #: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:5
2204 msgid "Edit repos group"
2150 msgid "Edit repos group"
2205 msgstr "编辑版本库组"
2151 msgstr "编辑版本库组"
2206
2152
2207 #: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:12
2153 #: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:12
2208 msgid "edit repos group"
2154 msgid "edit repos group"
2209 msgstr "编辑版本库组"
2155 msgstr "编辑版本库组"
2210
2156
2211 #: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:70
2157 #: rhodecode/templates/admin/repos_groups/repos_groups_edit.html:70
2212 msgid ""
2158 msgid ""
2213 "Enable lock-by-pulling on group. This option will be applied to all other"
2159 "Enable lock-by-pulling on group. This option will be applied to all other "
2214 " groups and repositories inside"
2160 "groups and repositories inside"
2215 msgstr ""
2161 msgstr "启用组的拉取锁定。这个选项将应用到组内的其他组和版本库"
2216
2162
2217 #: rhodecode/templates/admin/repos_groups/repos_groups_show.html:5
2163 #: rhodecode/templates/admin/repos_groups/repos_groups_show.html:5
2218 msgid "Repositories groups administration"
2164 msgid "Repositories groups administration"
2219 msgstr "版本库管理员"
2165 msgstr "版本库管理员"
2220
2166
2221 #: rhodecode/templates/admin/repos_groups/repos_groups_show.html:22
2167 #: rhodecode/templates/admin/repos_groups/repos_groups_show.html:22
2222 msgid "ADD NEW GROUP"
2168 msgid "ADD NEW GROUP"
2223 msgstr "添加组"
2169 msgstr "添加组"
2224
2170
2225 #: rhodecode/templates/admin/repos_groups/repos_groups_show.html:35
2171 #: rhodecode/templates/admin/repos_groups/repos_groups_show.html:35
2226 #, fuzzy
2227 msgid "Number of toplevel repositories"
2172 msgid "Number of toplevel repositories"
2228 msgstr "版本库数量"
2173 msgstr "顶层版本库数量"
2229
2174
2230 #: rhodecode/templates/admin/repos_groups/repos_groups_show.html:36
2175 #: rhodecode/templates/admin/repos_groups/repos_groups_show.html:36
2231 #: rhodecode/templates/admin/users/users.html:87
2176 #: rhodecode/templates/admin/users/users.html:87
2232 #: rhodecode/templates/admin/users_groups/users_groups.html:35
2177 #: rhodecode/templates/admin/users_groups/users_groups.html:35
2233 msgid "action"
2178 msgid "action"
2234 msgstr "操作"
2179 msgstr "操作"
2235
2180
2236 #: rhodecode/templates/admin/repos_groups/repos_groups_show.html:54
2181 #: rhodecode/templates/admin/repos_groups/repos_groups_show.html:54
2237 #: rhodecode/templates/admin/users/user_edit.html:255
2182 #: rhodecode/templates/admin/users/user_edit.html:255
2238 #: rhodecode/templates/admin/users_groups/users_groups.html:44
2183 #: rhodecode/templates/admin/users_groups/users_groups.html:44
2239 #: rhodecode/templates/data_table/_dt_elements.html:7
2184 #: rhodecode/templates/data_table/_dt_elements.html:7
2240 #: rhodecode/templates/data_table/_dt_elements.html:103
2185 #: rhodecode/templates/data_table/_dt_elements.html:103
2241 msgid "delete"
2186 msgid "delete"
2242 msgstr "删除"
2187 msgstr "删除"
2243
2188
2244 #: rhodecode/templates/admin/repos_groups/repos_groups_show.html:54
2189 #: rhodecode/templates/admin/repos_groups/repos_groups_show.html:54
2245 #, fuzzy, python-format
2190 #, python-format
2246 msgid "Confirm to delete this group: %s"
2191 msgid "Confirm to delete this group: %s"
2247 msgstr "确认删除该组"
2192 msgstr "确认删除该版本库: %s"
2248
2193
2249 #: rhodecode/templates/admin/repos_groups/repos_groups_show.html:62
2194 #: rhodecode/templates/admin/repos_groups/repos_groups_show.html:62
2250 msgid "There are no repositories groups yet"
2195 msgid "There are no repositories groups yet"
2251 msgstr "没有版本库组"
2196 msgstr "没有版本库组"
2252
2197
2253 #: rhodecode/templates/admin/settings/hooks.html:5
2198 #: rhodecode/templates/admin/settings/hooks.html:5
2254 #: rhodecode/templates/admin/settings/settings.html:5
2199 #: rhodecode/templates/admin/settings/settings.html:5
2255 msgid "Settings administration"
2200 msgid "Settings administration"
2256 msgstr "设置管理员"
2201 msgstr "设置管理员"
2257
2202
2258 #: rhodecode/templates/admin/settings/hooks.html:9
2203 #: rhodecode/templates/admin/settings/hooks.html:9
2259 #: rhodecode/templates/admin/settings/settings.html:9
2204 #: rhodecode/templates/admin/settings/settings.html:9
2260 #: rhodecode/templates/settings/repo_settings.html:13
2205 #: rhodecode/templates/settings/repo_settings.html:13
2261 msgid "Settings"
2206 msgid "Settings"
2262 msgstr "设置"
2207 msgstr "设置"
2263
2208
2264 #: rhodecode/templates/admin/settings/hooks.html:24
2209 #: rhodecode/templates/admin/settings/hooks.html:24
2265 msgid "Built in hooks - read only"
2210 msgid "Built in hooks - read only"
2266 msgstr "内建钩子 - 只读"
2211 msgstr "内建钩子 - 只读"
2267
2212
2268 #: rhodecode/templates/admin/settings/hooks.html:40
2213 #: rhodecode/templates/admin/settings/hooks.html:40
2269 msgid "Custom hooks"
2214 msgid "Custom hooks"
2270 msgstr "自定义钩子"
2215 msgstr "自定义钩子"
2271
2216
2272 #: rhodecode/templates/admin/settings/hooks.html:56
2217 #: rhodecode/templates/admin/settings/hooks.html:56
2273 msgid "remove"
2218 msgid "remove"
2274 msgstr "删除"
2219 msgstr "删除"
2275
2220
2276 #: rhodecode/templates/admin/settings/hooks.html:88
2221 #: rhodecode/templates/admin/settings/hooks.html:88
2277 msgid "Failed to remove hook"
2222 msgid "Failed to remove hook"
2278 msgstr "移除钩子失败"
2223 msgstr "移除钩子失败"
2279
2224
2280 #: rhodecode/templates/admin/settings/settings.html:24
2225 #: rhodecode/templates/admin/settings/settings.html:24
2281 msgid "Remap and rescan repositories"
2226 msgid "Remap and rescan repositories"
2282 msgstr "重新扫描并映射版本库"
2227 msgstr "重新扫描并映射版本库"
2283
2228
2284 #: rhodecode/templates/admin/settings/settings.html:32
2229 #: rhodecode/templates/admin/settings/settings.html:32
2285 msgid "rescan option"
2230 msgid "rescan option"
2286 msgstr "重新扫描选项"
2231 msgstr "重新扫描选项"
2287
2232
2288 #: rhodecode/templates/admin/settings/settings.html:38
2233 #: rhodecode/templates/admin/settings/settings.html:38
2289 msgid ""
2234 msgid ""
2290 "In case a repository was deleted from filesystem and there are leftovers "
2235 "In case a repository was deleted from filesystem and there are leftovers in "
2291 "in the database check this option to scan obsolete data in database and "
2236 "the database check this option to scan obsolete data in database and remove "
2292 "remove it."
2237 "it."
2293 msgstr "如果版本库已经从文件系统删除,但数据库仍然有遗留信息,请勾选该项进行清理"
2238 msgstr ""
2239 "如果版本库已经从文件系统删除,但数据库仍然有遗留信息,请勾选该项进行清理"
2294
2240
2295 #: rhodecode/templates/admin/settings/settings.html:39
2241 #: rhodecode/templates/admin/settings/settings.html:39
2296 msgid "destroy old data"
2242 msgid "destroy old data"
2297 msgstr "清理旧数据"
2243 msgstr "清理旧数据"
2298
2244
2299 #: rhodecode/templates/admin/settings/settings.html:41
2245 #: rhodecode/templates/admin/settings/settings.html:41
2300 msgid ""
2246 msgid ""
2301 "Rescan repositories location for new repositories. Also deletes obsolete "
2247 "Rescan repositories location for new repositories. Also deletes obsolete if "
2302 "if `destroy` flag is checked "
2248 "`destroy` flag is checked "
2303 msgstr ""
2249 msgstr ""
2250 "重新扫描版本库路径以发现新版本库。 同时删除过时的,如果设置有 `destroy` 标志"
2304
2251
2305 #: rhodecode/templates/admin/settings/settings.html:46
2252 #: rhodecode/templates/admin/settings/settings.html:46
2306 msgid "Rescan repositories"
2253 msgid "Rescan repositories"
2307 msgstr "重新扫描版本库"
2254 msgstr "重新扫描版本库"
2308
2255
2309 #: rhodecode/templates/admin/settings/settings.html:52
2256 #: rhodecode/templates/admin/settings/settings.html:52
2310 msgid "Whoosh indexing"
2257 msgid "Whoosh indexing"
2311 msgstr "Whoosh 索引"
2258 msgstr "Whoosh 索引"
2312
2259
2313 #: rhodecode/templates/admin/settings/settings.html:60
2260 #: rhodecode/templates/admin/settings/settings.html:60
2314 msgid "index build option"
2261 msgid "index build option"
2315 msgstr "构建索引选项"
2262 msgstr "构建索引选项"
2316
2263
2317 #: rhodecode/templates/admin/settings/settings.html:65
2264 #: rhodecode/templates/admin/settings/settings.html:65
2318 msgid "build from scratch"
2265 msgid "build from scratch"
2319 msgstr "重新建立"
2266 msgstr "重新建立"
2320
2267
2321 #: rhodecode/templates/admin/settings/settings.html:71
2268 #: rhodecode/templates/admin/settings/settings.html:71
2322 msgid "Reindex"
2269 msgid "Reindex"
2323 msgstr "重新索引"
2270 msgstr "重新索引"
2324
2271
2325 #: rhodecode/templates/admin/settings/settings.html:77
2272 #: rhodecode/templates/admin/settings/settings.html:77
2326 msgid "Global application settings"
2273 msgid "Global application settings"
2327 msgstr "全局设置"
2274 msgstr "全局设置"
2328
2275
2329 #: rhodecode/templates/admin/settings/settings.html:86
2276 #: rhodecode/templates/admin/settings/settings.html:86
2330 msgid "Application name"
2277 msgid "Application name"
2331 msgstr "应用名称"
2278 msgstr "应用名称"
2332
2279
2333 #: rhodecode/templates/admin/settings/settings.html:95
2280 #: rhodecode/templates/admin/settings/settings.html:95
2334 msgid "Realm text"
2281 msgid "Realm text"
2335 msgstr ""
2282 msgstr "Realm text"
2336
2283
2337 #: rhodecode/templates/admin/settings/settings.html:104
2284 #: rhodecode/templates/admin/settings/settings.html:104
2338 msgid "GA code"
2285 msgid "GA code"
2339 msgstr ""
2286 msgstr "GA code"
2340
2287
2341 #: rhodecode/templates/admin/settings/settings.html:112
2288 #: rhodecode/templates/admin/settings/settings.html:112
2342 #: rhodecode/templates/admin/settings/settings.html:167
2289 #: rhodecode/templates/admin/settings/settings.html:167
2343 #: rhodecode/templates/admin/settings/settings.html:257
2290 #: rhodecode/templates/admin/settings/settings.html:257
2344 msgid "Save settings"
2291 msgid "Save settings"
2345 msgstr "保存设置"
2292 msgstr "保存设置"
2346
2293
2347 #: rhodecode/templates/admin/settings/settings.html:119
2294 #: rhodecode/templates/admin/settings/settings.html:119
2348 #, fuzzy
2349 msgid "Visualisation settings"
2295 msgid "Visualisation settings"
2350 msgstr "全局设置"
2296 msgstr "可视化设置"
2351
2297
2352 #: rhodecode/templates/admin/settings/settings.html:128
2298 #: rhodecode/templates/admin/settings/settings.html:128
2353 #, fuzzy
2354 msgid "Icons"
2299 msgid "Icons"
2355 msgstr "选项"
2300 msgstr "图标"
2356
2301
2357 #: rhodecode/templates/admin/settings/settings.html:133
2302 #: rhodecode/templates/admin/settings/settings.html:133
2358 msgid "Show public repo icon on repositories"
2303 msgid "Show public repo icon on repositories"
2359 msgstr ""
2304 msgstr "显示公共版本库图标"
2360
2305
2361 #: rhodecode/templates/admin/settings/settings.html:137
2306 #: rhodecode/templates/admin/settings/settings.html:137
2362 #, fuzzy
2363 msgid "Show private repo icon on repositories"
2307 msgid "Show private repo icon on repositories"
2364 msgstr "私有版本库"
2308 msgstr "显示私有版本库图标"
2365
2309
2366 #: rhodecode/templates/admin/settings/settings.html:144
2310 #: rhodecode/templates/admin/settings/settings.html:144
2367 #, fuzzy
2368 msgid "Meta-Tagging"
2311 msgid "Meta-Tagging"
2369 msgstr "设置"
2312 msgstr "元标记"
2370
2313
2371 #: rhodecode/templates/admin/settings/settings.html:149
2314 #: rhodecode/templates/admin/settings/settings.html:149
2372 msgid "Stylify recognised metatags:"
2315 msgid "Stylify recognised metatags:"
2373 msgstr ""
2316 msgstr "样式化识别的元标记"
2374
2317
2375 #: rhodecode/templates/admin/settings/settings.html:176
2318 #: rhodecode/templates/admin/settings/settings.html:176
2376 #, fuzzy
2377 msgid "VCS settings"
2319 msgid "VCS settings"
2378 msgstr "设置"
2320 msgstr "版本控制系统设置"
2379
2321
2380 #: rhodecode/templates/admin/settings/settings.html:185
2322 #: rhodecode/templates/admin/settings/settings.html:185
2381 msgid "Web"
2323 msgid "Web"
2382 msgstr ""
2324 msgstr "网络"
2383
2325
2384 #: rhodecode/templates/admin/settings/settings.html:190
2326 #: rhodecode/templates/admin/settings/settings.html:190
2385 #, fuzzy
2386 msgid "require ssl for vcs operations"
2327 msgid "require ssl for vcs operations"
2387 msgstr "使用 SSL 推送"
2328 msgstr "要求使用 SSL进行版本控制系统操作"
2388
2329
2389 #: rhodecode/templates/admin/settings/settings.html:192
2330 #: rhodecode/templates/admin/settings/settings.html:192
2390 msgid ""
2331 msgid ""
2391 "RhodeCode will require SSL for pushing or pulling. If SSL is missing it "
2332 "RhodeCode will require SSL for pushing or pulling. If SSL is missing it will "
2392 "will return HTTP Error 406: Not Acceptable"
2333 "return HTTP Error 406: Not Acceptable"
2393 msgstr ""
2334 msgstr ""
2335 "勾选后 RhodeCode 将要求使用 SSL 进行推送和拉取。如果没有使用 SSL 将返回 HTTP "
2336 "406错误:Not Acceptable"
2394
2337
2395 #: rhodecode/templates/admin/settings/settings.html:198
2338 #: rhodecode/templates/admin/settings/settings.html:198
2396 msgid "Hooks"
2339 msgid "Hooks"
2397 msgstr ""
2340 msgstr "钩子"
2398
2341
2399 #: rhodecode/templates/admin/settings/settings.html:203
2342 #: rhodecode/templates/admin/settings/settings.html:203
2400 msgid "Update repository after push (hg update)"
2343 msgid "Update repository after push (hg update)"
2401 msgstr "推送后更新版本库(hg update)"
2344 msgstr "推送后更新版本库(hg update)"
2402
2345
2403 #: rhodecode/templates/admin/settings/settings.html:207
2346 #: rhodecode/templates/admin/settings/settings.html:207
2404 msgid "Show repository size after push"
2347 msgid "Show repository size after push"
2405 msgstr "推送后显示版本库大小"
2348 msgstr "推送后显示版本库大小"
2406
2349
2407 #: rhodecode/templates/admin/settings/settings.html:211
2350 #: rhodecode/templates/admin/settings/settings.html:211
2408 msgid "Log user push commands"
2351 msgid "Log user push commands"
2409 msgstr "记录用户推送命令"
2352 msgstr "记录用户推送命令"
2410
2353
2411 #: rhodecode/templates/admin/settings/settings.html:215
2354 #: rhodecode/templates/admin/settings/settings.html:215
2412 msgid "Log user pull commands"
2355 msgid "Log user pull commands"
2413 msgstr "记录用户拉取命令"
2356 msgstr "记录用户拉取命令"
2414
2357
2415 #: rhodecode/templates/admin/settings/settings.html:219
2358 #: rhodecode/templates/admin/settings/settings.html:219
2416 msgid "advanced setup"
2359 msgid "advanced setup"
2417 msgstr "高级设置"
2360 msgstr "高级设置"
2418
2361
2419 #: rhodecode/templates/admin/settings/settings.html:224
2362 #: rhodecode/templates/admin/settings/settings.html:224
2420 #, fuzzy
2421 msgid "Mercurial Extensions"
2363 msgid "Mercurial Extensions"
2422 msgstr "Mercurial 版本库"
2364 msgstr "Mercurial 扩展"
2423
2365
2424 #: rhodecode/templates/admin/settings/settings.html:229
2366 #: rhodecode/templates/admin/settings/settings.html:229
2425 msgid "largefiles extensions"
2367 msgid "largefiles extensions"
2426 msgstr ""
2368 msgstr "大文件扩展"
2427
2369
2428 #: rhodecode/templates/admin/settings/settings.html:233
2370 #: rhodecode/templates/admin/settings/settings.html:233
2429 msgid "hgsubversion extensions"
2371 msgid "hgsubversion extensions"
2430 msgstr ""
2372 msgstr "hgsubversion 扩展"
2431
2373
2432 #: rhodecode/templates/admin/settings/settings.html:235
2374 #: rhodecode/templates/admin/settings/settings.html:235
2433 msgid ""
2375 msgid ""
2434 "Requires hgsubversion library installed. Allows clonning from svn remote "
2376 "Requires hgsubversion library installed. Allows clonning from svn remote "
2435 "locations"
2377 "locations"
2436 msgstr ""
2378 msgstr " 允许从远程 svn 地址克隆。需要安装 hgsubversion 库"
2437
2379
2438 #: rhodecode/templates/admin/settings/settings.html:245
2380 #: rhodecode/templates/admin/settings/settings.html:245
2439 msgid "Repositories location"
2381 msgid "Repositories location"
2440 msgstr "版本库路径"
2382 msgstr "版本库路径"
2441
2383
2442 #: rhodecode/templates/admin/settings/settings.html:250
2384 #: rhodecode/templates/admin/settings/settings.html:250
2443 msgid ""
2385 msgid ""
2444 "This a crucial application setting. If you are really sure you need to "
2386 "This a crucial application setting. If you are really sure you need to "
2445 "change this, you must restart application in order to make this setting "
2387 "change this, you must restart application in order to make this setting take "
2446 "take effect. Click this label to unlock."
2388 "effect. Click this label to unlock."
2447 msgstr "这是一个关键设置。如果确认修改该项设置,请重启服务以便设置生效。"
2389 msgstr "这是一个关键设置。如果确认修改该项设置,请重启服务以便设置生效。"
2448
2390
2449 #: rhodecode/templates/admin/settings/settings.html:251
2391 #: rhodecode/templates/admin/settings/settings.html:251
2450 msgid "unlock"
2392 msgid "unlock"
2451 msgstr "解锁"
2393 msgstr "解锁"
2452
2394
2453 #: rhodecode/templates/admin/settings/settings.html:252
2395 #: rhodecode/templates/admin/settings/settings.html:252
2454 msgid ""
2396 msgid ""
2455 "Location where repositories are stored. After changing this value a "
2397 "Location where repositories are stored. After changing this value a restart, "
2456 "restart, and rescan is required"
2398 "and rescan is required"
2457 msgstr ""
2399 msgstr "版本库存储路径。 修改后需要重启和重新扫描"
2458
2400
2459 #: rhodecode/templates/admin/settings/settings.html:272
2401 #: rhodecode/templates/admin/settings/settings.html:272
2460 msgid "Test Email"
2402 msgid "Test Email"
2461 msgstr ""
2403 msgstr "测试邮件"
2462
2404
2463 #: rhodecode/templates/admin/settings/settings.html:280
2405 #: rhodecode/templates/admin/settings/settings.html:280
2464 #, fuzzy
2465 msgid "Email to"
2406 msgid "Email to"
2466 msgstr "电子邮件"
2407 msgstr "发送到"
2467
2408
2468 #: rhodecode/templates/admin/settings/settings.html:288
2409 #: rhodecode/templates/admin/settings/settings.html:288
2469 #, fuzzy
2470 msgid "Send"
2410 msgid "Send"
2471 msgstr ""
2411 msgstr "发送"
2472
2412
2473 #: rhodecode/templates/admin/settings/settings.html:294
2413 #: rhodecode/templates/admin/settings/settings.html:294
2474 msgid "System Info and Packages"
2414 msgid "System Info and Packages"
2475 msgstr ""
2415 msgstr "系统和软件包信息"
2476
2416
2477 #: rhodecode/templates/admin/settings/settings.html:297
2417 #: rhodecode/templates/admin/settings/settings.html:297
2478 #, fuzzy
2479 msgid "show"
2418 msgid "show"
2480 msgstr "显示"
2419 msgstr "显示"
2481
2420
2482 #: rhodecode/templates/admin/users/user_add.html:5
2421 #: rhodecode/templates/admin/users/user_add.html:5
2483 msgid "Add user"
2422 msgid "Add user"
2484 msgstr "添加用户"
2423 msgstr "添加用户"
2485
2424
2486 #: rhodecode/templates/admin/users/user_add.html:10
2425 #: rhodecode/templates/admin/users/user_add.html:10
2487 #: rhodecode/templates/admin/users/user_edit.html:11
2426 #: rhodecode/templates/admin/users/user_edit.html:11
2488 msgid "Users"
2427 msgid "Users"
2489 msgstr "用户"
2428 msgstr "用户"
2490
2429
2491 #: rhodecode/templates/admin/users/user_add.html:12
2430 #: rhodecode/templates/admin/users/user_add.html:12
2492 msgid "add new user"
2431 msgid "add new user"
2493 msgstr "添加新用户"
2432 msgstr "添加新用户"
2494
2433
2495 #: rhodecode/templates/admin/users/user_add.html:50
2434 #: rhodecode/templates/admin/users/user_add.html:50
2496 #, fuzzy
2497 msgid "Password confirmation"
2435 msgid "Password confirmation"
2498 msgstr "密码不符"
2436 msgstr "确认密码"
2499
2437
2500 #: rhodecode/templates/admin/users/user_add.html:86
2438 #: rhodecode/templates/admin/users/user_add.html:86
2501 #: rhodecode/templates/admin/users/user_edit.html:113
2439 #: rhodecode/templates/admin/users/user_edit.html:113
2502 #: rhodecode/templates/admin/users_groups/users_group_add.html:41
2440 #: rhodecode/templates/admin/users_groups/users_group_add.html:41
2503 #: rhodecode/templates/admin/users_groups/users_group_edit.html:42
2441 #: rhodecode/templates/admin/users_groups/users_group_edit.html:42
2504 msgid "Active"
2442 msgid "Active"
2505 msgstr "启用"
2443 msgstr "启用"
2506
2444
2507 #: rhodecode/templates/admin/users/user_edit.html:5
2445 #: rhodecode/templates/admin/users/user_edit.html:5
2508 msgid "Edit user"
2446 msgid "Edit user"
2509 msgstr "编辑用户"
2447 msgstr "编辑用户"
2510
2448
2511 #: rhodecode/templates/admin/users/user_edit.html:34
2449 #: rhodecode/templates/admin/users/user_edit.html:34
2512 #: rhodecode/templates/admin/users/user_edit_my_account_form.html:10
2450 #: rhodecode/templates/admin/users/user_edit_my_account_form.html:10
2513 msgid "Change your avatar at"
2451 msgid "Change your avatar at"
2514 msgstr "修改你的头像"
2452 msgstr "修改你的头像"
2515
2453
2516 #: rhodecode/templates/admin/users/user_edit.html:35
2454 #: rhodecode/templates/admin/users/user_edit.html:35
2517 #: rhodecode/templates/admin/users/user_edit_my_account_form.html:11
2455 #: rhodecode/templates/admin/users/user_edit_my_account_form.html:11
2518 msgid "Using"
2456 msgid "Using"
2519 msgstr "使用中"
2457 msgstr "使用中"
2520
2458
2521 #: rhodecode/templates/admin/users/user_edit.html:43
2459 #: rhodecode/templates/admin/users/user_edit.html:43
2522 #: rhodecode/templates/admin/users/user_edit_my_account_form.html:20
2460 #: rhodecode/templates/admin/users/user_edit_my_account_form.html:20
2523 msgid "API key"
2461 msgid "API key"
2524 msgstr ""
2462 msgstr "API 密钥"
2525
2463
2526 #: rhodecode/templates/admin/users/user_edit.html:59
2464 #: rhodecode/templates/admin/users/user_edit.html:59
2527 msgid "LDAP DN"
2465 msgid "LDAP DN"
2528 msgstr ""
2466 msgstr "LDAP DN"
2529
2467
2530 #: rhodecode/templates/admin/users/user_edit.html:68
2468 #: rhodecode/templates/admin/users/user_edit.html:68
2531 #: rhodecode/templates/admin/users/user_edit_my_account_form.html:35
2469 #: rhodecode/templates/admin/users/user_edit_my_account_form.html:35
2532 msgid "New password"
2470 msgid "New password"
2533 msgstr "新密码"
2471 msgstr "新密码"
2534
2472
2535 #: rhodecode/templates/admin/users/user_edit.html:77
2473 #: rhodecode/templates/admin/users/user_edit.html:77
2536 #: rhodecode/templates/admin/users/user_edit_my_account_form.html:44
2474 #: rhodecode/templates/admin/users/user_edit_my_account_form.html:44
2537 msgid "New password confirmation"
2475 msgid "New password confirmation"
2538 msgstr ""
2476 msgstr "确认新密码"
2539
2477
2540 #: rhodecode/templates/admin/users/user_edit.html:147
2478 #: rhodecode/templates/admin/users/user_edit.html:147
2541 #: rhodecode/templates/admin/users_groups/users_group_edit.html:108
2479 #: rhodecode/templates/admin/users_groups/users_group_edit.html:108
2542 #, fuzzy
2543 msgid "Inherit default permissions"
2480 msgid "Inherit default permissions"
2544 msgstr "默认权限"
2481 msgstr "继承默认权限"
2545
2482
2546 #: rhodecode/templates/admin/users/user_edit.html:152
2483 #: rhodecode/templates/admin/users/user_edit.html:152
2547 #: rhodecode/templates/admin/users_groups/users_group_edit.html:113
2484 #: rhodecode/templates/admin/users_groups/users_group_edit.html:113
2548 #, python-format
2485 #, python-format
2549 msgid ""
2486 msgid ""
2550 "Select to inherit permissions from %s settings. With this selected below "
2487 "Select to inherit permissions from %s settings. With this selected below "
2551 "options does not have any action"
2488 "options does not have any action"
2552 msgstr ""
2489 msgstr "勾选以从 %s 继承权限设置。 勾选后下面的选项将不起作用"
2553
2490
2554 #: rhodecode/templates/admin/users/user_edit.html:158
2491 #: rhodecode/templates/admin/users/user_edit.html:158
2555 #: rhodecode/templates/admin/users_groups/users_group_edit.html:119
2492 #: rhodecode/templates/admin/users_groups/users_group_edit.html:119
2556 msgid "Create repositories"
2493 msgid "Create repositories"
2557 msgstr "创建版本库"
2494 msgstr "创建版本库"
2558
2495
2559 #: rhodecode/templates/admin/users/user_edit.html:166
2496 #: rhodecode/templates/admin/users/user_edit.html:166
2560 #: rhodecode/templates/admin/users_groups/users_group_edit.html:127
2497 #: rhodecode/templates/admin/users_groups/users_group_edit.html:127
2561 #, fuzzy
2562 msgid "Fork repositories"
2498 msgid "Fork repositories"
2563 msgstr "版本库"
2499 msgstr "分支版本库"
2564
2500
2565 #: rhodecode/templates/admin/users/user_edit.html:186
2501 #: rhodecode/templates/admin/users/user_edit.html:186
2566 #: rhodecode/templates/admin/users/user_edit_my_account_pullrequests.html:22
2502 #: rhodecode/templates/admin/users/user_edit_my_account_pullrequests.html:22
2567 #: rhodecode/templates/admin/users/user_edit_my_account_pullrequests.html:39
2503 #: rhodecode/templates/admin/users/user_edit_my_account_pullrequests.html:39
2568 #, fuzzy
2569 msgid "Nothing here yet"
2504 msgid "Nothing here yet"
2570 msgstr "尚无操作"
2505 msgstr "无条目"
2571
2506
2572 #: rhodecode/templates/admin/users/user_edit.html:193
2507 #: rhodecode/templates/admin/users/user_edit.html:193
2573 #: rhodecode/templates/admin/users/user_edit_my_account.html:60
2508 #: rhodecode/templates/admin/users/user_edit_my_account.html:60
2574 #: rhodecode/templates/admin/users/user_edit_my_account.html:194
2509 #: rhodecode/templates/admin/users/user_edit_my_account.html:194
2575 #, fuzzy
2576 msgid "Permission"
2510 msgid "Permission"
2577 msgstr "权限"
2511 msgstr "权限"
2578
2512
2579 #: rhodecode/templates/admin/users/user_edit.html:194
2513 #: rhodecode/templates/admin/users/user_edit.html:194
2580 #, fuzzy
2581 msgid "Edit Permission"
2514 msgid "Edit Permission"
2582 msgstr "版本库权限"
2515 msgstr "编辑权限"
2583
2516
2584 #: rhodecode/templates/admin/users/user_edit.html:243
2517 #: rhodecode/templates/admin/users/user_edit.html:243
2585 #, fuzzy
2586 msgid "Email addresses"
2518 msgid "Email addresses"
2587 msgstr "邮件地址"
2519 msgstr "邮件地址"
2588
2520
2589 #: rhodecode/templates/admin/users/user_edit.html:256
2521 #: rhodecode/templates/admin/users/user_edit.html:256
2590 #, fuzzy, python-format
2522 #, python-format
2591 msgid "Confirm to delete this email: %s"
2523 msgid "Confirm to delete this email: %s"
2592 msgstr "确认删除该用户"
2524 msgstr "确认删除邮件地址: %s"
2593
2525
2594 #: rhodecode/templates/admin/users/user_edit.html:270
2526 #: rhodecode/templates/admin/users/user_edit.html:270
2595 #, fuzzy
2596 msgid "New email address"
2527 msgid "New email address"
2597 msgstr "邮件地址"
2528 msgstr "增加邮件地址"
2598
2529
2599 #: rhodecode/templates/admin/users/user_edit.html:277
2530 #: rhodecode/templates/admin/users/user_edit.html:277
2600 #, fuzzy
2601 msgid "Add"
2531 msgid "Add"
2602 msgstr "增"
2532 msgstr "增"
2603
2533
2604 #: rhodecode/templates/admin/users/user_edit_my_account.html:5
2534 #: rhodecode/templates/admin/users/user_edit_my_account.html:5
2605 #: rhodecode/templates/base/base.html:124
2535 #: rhodecode/templates/base/base.html:124
2606 msgid "My account"
2536 msgid "My account"
2607 msgstr "我的账户"
2537 msgstr "我的账户"
2608
2538
2609 #: rhodecode/templates/admin/users/user_edit_my_account.html:9
2539 #: rhodecode/templates/admin/users/user_edit_my_account.html:9
2610 msgid "My Account"
2540 msgid "My Account"
2611 msgstr "我的账户"
2541 msgstr "我的账户"
2612
2542
2613 #: rhodecode/templates/admin/users/user_edit_my_account.html:35
2543 #: rhodecode/templates/admin/users/user_edit_my_account.html:35
2614 #, fuzzy
2615 msgid "My permissions"
2544 msgid "My permissions"
2616 msgstr "权限"
2545 msgstr "我的权限"
2617
2546
2618 #: rhodecode/templates/admin/users/user_edit_my_account.html:38
2547 #: rhodecode/templates/admin/users/user_edit_my_account.html:38
2619 #: rhodecode/templates/journal/journal.html:41
2548 #: rhodecode/templates/journal/journal.html:41
2620 #, fuzzy
2621 msgid "My repos"
2549 msgid "My repos"
2622 msgstr "版本库"
2550 msgstr "我的版本库"
2623
2551
2624 #: rhodecode/templates/admin/users/user_edit_my_account.html:41
2552 #: rhodecode/templates/admin/users/user_edit_my_account.html:41
2625 #, fuzzy
2626 msgid "My pull requests"
2553 msgid "My pull requests"
2627 msgstr "创建用户 %s"
2554 msgstr "我的拉取请求"
2628
2555
2629 #: rhodecode/templates/admin/users/user_edit_my_account.html:45
2556 #: rhodecode/templates/admin/users/user_edit_my_account.html:45
2630 #, fuzzy
2631 msgid "Add repo"
2557 msgid "Add repo"
2632 msgstr "新"
2558 msgstr "新建版本库"
2633
2559
2634 #: rhodecode/templates/admin/users/user_edit_my_account_pullrequests.html:2
2560 #: rhodecode/templates/admin/users/user_edit_my_account_pullrequests.html:2
2635 msgid "Opened by me"
2561 msgid "Opened by me"
2636 msgstr ""
2562 msgstr "我创建的"
2637
2563
2638 #: rhodecode/templates/admin/users/user_edit_my_account_pullrequests.html:10
2564 #: rhodecode/templates/admin/users/user_edit_my_account_pullrequests.html:10
2639 #, python-format
2565 #, python-format
2640 msgid "Pull request #%s opened on %s"
2566 msgid "Pull request #%s opened on %s"
2641 msgstr ""
2567 msgstr "拉取请求 #%s 创建于 %s"
2642
2568
2643 #: rhodecode/templates/admin/users/user_edit_my_account_pullrequests.html:15
2569 #: rhodecode/templates/admin/users/user_edit_my_account_pullrequests.html:15
2644 #, fuzzy
2645 msgid "Confirm to delete this pull request"
2570 msgid "Confirm to delete this pull request"
2646 msgstr "确认删除版本库"
2571 msgstr "确认删除拉取请求"
2647
2572
2648 #: rhodecode/templates/admin/users/user_edit_my_account_pullrequests.html:26
2573 #: rhodecode/templates/admin/users/user_edit_my_account_pullrequests.html:26
2649 msgid "I participate in"
2574 msgid "I participate in"
2650 msgstr ""
2575 msgstr "我参与的"
2651
2576
2652 #: rhodecode/templates/admin/users/user_edit_my_account_pullrequests.html:33
2577 #: rhodecode/templates/admin/users/user_edit_my_account_pullrequests.html:33
2653 #: rhodecode/templates/pullrequests/pullrequest_show_all.html:30
2578 #: rhodecode/templates/pullrequests/pullrequest_show_all.html:30
2654 #, python-format
2579 #, python-format
2655 msgid "Pull request #%s opened by %s on %s"
2580 msgid "Pull request #%s opened by %s on %s"
2656 msgstr ""
2581 msgstr "拉取请求 #%s 由 %s 创建于 %s"
2657
2582
2658 #: rhodecode/templates/admin/users/user_edit_my_account_repos.html:7
2583 #: rhodecode/templates/admin/users/user_edit_my_account_repos.html:7
2659 #: rhodecode/templates/bookmarks/bookmarks.html:40
2584 #: rhodecode/templates/bookmarks/bookmarks.html:40
2660 #: rhodecode/templates/bookmarks/bookmarks_data.html:9
2585 #: rhodecode/templates/bookmarks/bookmarks_data.html:9
2661 #: rhodecode/templates/branches/branches.html:55
2586 #: rhodecode/templates/branches/branches.html:55
2662 #: rhodecode/templates/journal/journal.html:60
2587 #: rhodecode/templates/journal/journal.html:60
2663 #: rhodecode/templates/tags/tags.html:40
2588 #: rhodecode/templates/tags/tags.html:40
2664 #: rhodecode/templates/tags/tags_data.html:9
2589 #: rhodecode/templates/tags/tags_data.html:9
2665 msgid "Revision"
2590 msgid "Revision"
2666 msgstr "修订"
2591 msgstr "修订"
2667
2592
2668 #: rhodecode/templates/admin/users/user_edit_my_account_repos.html:28
2593 #: rhodecode/templates/admin/users/user_edit_my_account_repos.html:28
2669 #: rhodecode/templates/journal/journal.html:81
2594 #: rhodecode/templates/journal/journal.html:81
2670 msgid "private"
2595 msgid "private"
2671 msgstr "私有"
2596 msgstr "私有"
2672
2597
2673 #: rhodecode/templates/admin/users/user_edit_my_account_repos.html:31
2598 #: rhodecode/templates/admin/users/user_edit_my_account_repos.html:31
2674 #: rhodecode/templates/data_table/_dt_elements.html:7
2599 #: rhodecode/templates/data_table/_dt_elements.html:7
2675 #, fuzzy, python-format
2600 #, python-format
2676 msgid "Confirm to delete this repository: %s"
2601 msgid "Confirm to delete this repository: %s"
2677 msgstr "确认删除版本库"
2602 msgstr "确认删除版本库: %s"
2678
2603
2679 #: rhodecode/templates/admin/users/user_edit_my_account_repos.html:38
2604 #: rhodecode/templates/admin/users/user_edit_my_account_repos.html:38
2680 #: rhodecode/templates/journal/journal.html:94
2605 #: rhodecode/templates/journal/journal.html:94
2681 msgid "No repositories yet"
2606 msgid "No repositories yet"
2682 msgstr "没有任何版本库"
2607 msgstr "没有任何版本库"
2683
2608
2684 #: rhodecode/templates/admin/users/user_edit_my_account_repos.html:40
2609 #: rhodecode/templates/admin/users/user_edit_my_account_repos.html:40
2685 #: rhodecode/templates/journal/journal.html:96
2610 #: rhodecode/templates/journal/journal.html:96
2686 msgid "create one now"
2611 msgid "create one now"
2687 msgstr ""
2612 msgstr "创建一个"
2688
2613
2689 #: rhodecode/templates/admin/users/users.html:5
2614 #: rhodecode/templates/admin/users/users.html:5
2690 msgid "Users administration"
2615 msgid "Users administration"
2691 msgstr "用户管理员"
2616 msgstr "用户管理员"
2692
2617
2693 #: rhodecode/templates/admin/users/users.html:9
2618 #: rhodecode/templates/admin/users/users.html:9
2694 #: rhodecode/templates/base/base.html:223
2619 #: rhodecode/templates/base/base.html:223
2695 msgid "users"
2620 msgid "users"
2696 msgstr "用户"
2621 msgstr "用户"
2697
2622
2698 #: rhodecode/templates/admin/users/users.html:23
2623 #: rhodecode/templates/admin/users/users.html:23
2699 msgid "ADD NEW USER"
2624 msgid "ADD NEW USER"
2700 msgstr "添加用户"
2625 msgstr "添加用户"
2701
2626
2702 #: rhodecode/templates/admin/users/users.html:77
2627 #: rhodecode/templates/admin/users/users.html:77
2703 msgid "username"
2628 msgid "username"
2704 msgstr "用户名"
2629 msgstr "用户名"
2705
2630
2706 #: rhodecode/templates/admin/users/users.html:80
2631 #: rhodecode/templates/admin/users/users.html:80
2707 #, fuzzy
2708 msgid "firstname"
2632 msgid "firstname"
2709 msgstr "名"
2633 msgstr "名"
2710
2634
2711 #: rhodecode/templates/admin/users/users.html:81
2635 #: rhodecode/templates/admin/users/users.html:81
2712 msgid "lastname"
2636 msgid "lastname"
2713 msgstr "姓"
2637 msgstr "姓"
2714
2638
2715 #: rhodecode/templates/admin/users/users.html:82
2639 #: rhodecode/templates/admin/users/users.html:82
2716 msgid "last login"
2640 msgid "last login"
2717 msgstr "最后登录"
2641 msgstr "最后登录"
2718
2642
2719 #: rhodecode/templates/admin/users/users.html:84
2643 #: rhodecode/templates/admin/users/users.html:84
2720 #: rhodecode/templates/admin/users_groups/users_groups.html:34
2644 #: rhodecode/templates/admin/users_groups/users_groups.html:34
2721 msgid "active"
2645 msgid "active"
2722 msgstr "启用"
2646 msgstr "启用"
2723
2647
2724 #: rhodecode/templates/admin/users/users.html:86
2648 #: rhodecode/templates/admin/users/users.html:86
2725 #: rhodecode/templates/base/base.html:226
2649 #: rhodecode/templates/base/base.html:226
2726 msgid "ldap"
2650 msgid "ldap"
2727 msgstr ""
2651 msgstr "LDAP"
2728
2652
2729 #: rhodecode/templates/admin/users_groups/users_group_add.html:5
2653 #: rhodecode/templates/admin/users_groups/users_group_add.html:5
2730 msgid "Add users group"
2654 msgid "Add users group"
2731 msgstr "添加用户组"
2655 msgstr "添加用户组"
2732
2656
2733 #: rhodecode/templates/admin/users_groups/users_group_add.html:10
2657 #: rhodecode/templates/admin/users_groups/users_group_add.html:10
2734 #: rhodecode/templates/admin/users_groups/users_groups.html:9
2658 #: rhodecode/templates/admin/users_groups/users_groups.html:9
2735 msgid "Users groups"
2659 msgid "Users groups"
2736 msgstr "用户组"
2660 msgstr "用户组"
2737
2661
2738 #: rhodecode/templates/admin/users_groups/users_group_add.html:12
2662 #: rhodecode/templates/admin/users_groups/users_group_add.html:12
2739 msgid "add new users group"
2663 msgid "add new users group"
2740 msgstr "添加新用户组"
2664 msgstr "添加新用户组"
2741
2665
2742 #: rhodecode/templates/admin/users_groups/users_group_edit.html:5
2666 #: rhodecode/templates/admin/users_groups/users_group_edit.html:5
2743 msgid "Edit users group"
2667 msgid "Edit users group"
2744 msgstr "编辑用户组"
2668 msgstr "编辑用户组"
2745
2669
2746 #: rhodecode/templates/admin/users_groups/users_group_edit.html:11
2670 #: rhodecode/templates/admin/users_groups/users_group_edit.html:11
2747 msgid "UsersGroups"
2671 msgid "UsersGroups"
2748 msgstr "用户组"
2672 msgstr "用户组"
2749
2673
2750 #: rhodecode/templates/admin/users_groups/users_group_edit.html:50
2674 #: rhodecode/templates/admin/users_groups/users_group_edit.html:50
2751 msgid "Members"
2675 msgid "Members"
2752 msgstr "成员"
2676 msgstr "成员"
2753
2677
2754 #: rhodecode/templates/admin/users_groups/users_group_edit.html:58
2678 #: rhodecode/templates/admin/users_groups/users_group_edit.html:58
2755 msgid "Choosen group members"
2679 msgid "Choosen group members"
2756 msgstr "选择组成员"
2680 msgstr "选择组成员"
2757
2681
2758 #: rhodecode/templates/admin/users_groups/users_group_edit.html:61
2682 #: rhodecode/templates/admin/users_groups/users_group_edit.html:61
2759 msgid "Remove all elements"
2683 msgid "Remove all elements"
2760 msgstr "移除全部项目"
2684 msgstr "移除全部项目"
2761
2685
2762 #: rhodecode/templates/admin/users_groups/users_group_edit.html:75
2686 #: rhodecode/templates/admin/users_groups/users_group_edit.html:75
2763 msgid "Available members"
2687 msgid "Available members"
2764 msgstr "启用成员"
2688 msgstr "启用成员"
2765
2689
2766 #: rhodecode/templates/admin/users_groups/users_group_edit.html:79
2690 #: rhodecode/templates/admin/users_groups/users_group_edit.html:79
2767 msgid "Add all elements"
2691 msgid "Add all elements"
2768 msgstr "添加全部项目"
2692 msgstr "添加全部项目"
2769
2693
2770 #: rhodecode/templates/admin/users_groups/users_group_edit.html:146
2694 #: rhodecode/templates/admin/users_groups/users_group_edit.html:146
2771 #, fuzzy
2772 msgid "Group members"
2695 msgid "Group members"
2773 msgstr "选择组成员"
2696 msgstr "拥护者成员"
2774
2697
2775 #: rhodecode/templates/admin/users_groups/users_groups.html:5
2698 #: rhodecode/templates/admin/users_groups/users_groups.html:5
2776 msgid "Users groups administration"
2699 msgid "Users groups administration"
2777 msgstr "用户组管理"
2700 msgstr "用户组管理"
2778
2701
2779 #: rhodecode/templates/admin/users_groups/users_groups.html:23
2702 #: rhodecode/templates/admin/users_groups/users_groups.html:23
2780 msgid "ADD NEW USER GROUP"
2703 msgid "ADD NEW USER GROUP"
2781 msgstr "添加新用户组"
2704 msgstr "添加新用户组"
2782
2705
2783 #: rhodecode/templates/admin/users_groups/users_groups.html:32
2706 #: rhodecode/templates/admin/users_groups/users_groups.html:32
2784 msgid "group name"
2707 msgid "group name"
2785 msgstr "组名"
2708 msgstr "组名"
2786
2709
2787 #: rhodecode/templates/admin/users_groups/users_groups.html:33
2710 #: rhodecode/templates/admin/users_groups/users_groups.html:33
2788 #: rhodecode/templates/base/root.html:46
2711 #: rhodecode/templates/base/root.html:46
2789 msgid "members"
2712 msgid "members"
2790 msgstr "成员"
2713 msgstr "成员"
2791
2714
2792 #: rhodecode/templates/admin/users_groups/users_groups.html:45
2715 #: rhodecode/templates/admin/users_groups/users_groups.html:45
2793 #, fuzzy, python-format
2716 #, python-format
2794 msgid "Confirm to delete this users group: %s"
2717 msgid "Confirm to delete this users group: %s"
2795 msgstr "确认删除该组"
2718 msgstr "确认删除该组: %s"
2796
2719
2797 #: rhodecode/templates/base/base.html:41
2720 #: rhodecode/templates/base/base.html:41
2798 msgid "Submit a bug"
2721 msgid "Submit a bug"
2799 msgstr "提交 bug"
2722 msgstr "提交 bug"
2800
2723
2801 #: rhodecode/templates/base/base.html:77
2724 #: rhodecode/templates/base/base.html:77
2802 msgid "Login to your account"
2725 msgid "Login to your account"
2803 msgstr ""
2726 msgstr "登录"
2804
2727
2805 #: rhodecode/templates/base/base.html:100
2728 #: rhodecode/templates/base/base.html:100
2806 msgid "Forgot password ?"
2729 msgid "Forgot password ?"
2807 msgstr "忘记密码?"
2730 msgstr "忘记密码?"
2808
2731
2809 #: rhodecode/templates/base/base.html:107
2732 #: rhodecode/templates/base/base.html:107
2810 #, fuzzy
2811 msgid "Log In"
2733 msgid "Log In"
2812 msgstr "登录"
2734 msgstr "登录"
2813
2735
2814 #: rhodecode/templates/base/base.html:118
2736 #: rhodecode/templates/base/base.html:118
2815 msgid "Inbox"
2737 msgid "Inbox"
2816 msgstr ""
2738 msgstr "收件箱"
2817
2739
2818 #: rhodecode/templates/base/base.html:122
2740 #: rhodecode/templates/base/base.html:122
2819 #: rhodecode/templates/base/base.html:300
2741 #: rhodecode/templates/base/base.html:300
2820 #: rhodecode/templates/base/base.html:302
2742 #: rhodecode/templates/base/base.html:302
2821 #: rhodecode/templates/base/base.html:304
2743 #: rhodecode/templates/base/base.html:304
2822 #: rhodecode/templates/bookmarks/bookmarks.html:11
2744 #: rhodecode/templates/bookmarks/bookmarks.html:11
2823 #: rhodecode/templates/branches/branches.html:10
2745 #: rhodecode/templates/branches/branches.html:10
2824 #: rhodecode/templates/changelog/changelog.html:10
2746 #: rhodecode/templates/changelog/changelog.html:10
2825 #: rhodecode/templates/changeset/changeset.html:10
2747 #: rhodecode/templates/changeset/changeset.html:10
2826 #: rhodecode/templates/changeset/changeset_range.html:9
2748 #: rhodecode/templates/changeset/changeset_range.html:9
2827 #: rhodecode/templates/compare/compare_diff.html:9
2749 #: rhodecode/templates/compare/compare_diff.html:9
2828 #: rhodecode/templates/files/file_diff.html:8
2750 #: rhodecode/templates/files/file_diff.html:8
2829 #: rhodecode/templates/files/files.html:8
2751 #: rhodecode/templates/files/files.html:8
2830 #: rhodecode/templates/files/files_add.html:15
2752 #: rhodecode/templates/files/files_add.html:15
2831 #: rhodecode/templates/files/files_edit.html:15
2753 #: rhodecode/templates/files/files_edit.html:15
2832 #: rhodecode/templates/followers/followers.html:9
2754 #: rhodecode/templates/followers/followers.html:9
2833 #: rhodecode/templates/forks/fork.html:9 rhodecode/templates/forks/forks.html:9
2755 #: rhodecode/templates/forks/fork.html:9
2756 #: rhodecode/templates/forks/forks.html:9
2834 #: rhodecode/templates/pullrequests/pullrequest.html:8
2757 #: rhodecode/templates/pullrequests/pullrequest.html:8
2835 #: rhodecode/templates/pullrequests/pullrequest_show.html:8
2758 #: rhodecode/templates/pullrequests/pullrequest_show.html:8
2836 #: rhodecode/templates/pullrequests/pullrequest_show_all.html:8
2759 #: rhodecode/templates/pullrequests/pullrequest_show_all.html:8
2837 #: rhodecode/templates/settings/repo_settings.html:9
2760 #: rhodecode/templates/settings/repo_settings.html:9
2838 #: rhodecode/templates/shortlog/shortlog.html:10
2761 #: rhodecode/templates/shortlog/shortlog.html:10
2839 #: rhodecode/templates/summary/summary.html:8
2762 #: rhodecode/templates/summary/summary.html:8
2840 #: rhodecode/templates/tags/tags.html:11
2763 #: rhodecode/templates/tags/tags.html:11
2841 msgid "Home"
2764 msgid "Home"
2842 msgstr "首页"
2765 msgstr "首页"
2843
2766
2844 #: rhodecode/templates/base/base.html:123
2767 #: rhodecode/templates/base/base.html:123
2845 #: rhodecode/templates/base/base.html:309
2768 #: rhodecode/templates/base/base.html:309
2846 #: rhodecode/templates/base/base.html:311
2769 #: rhodecode/templates/base/base.html:311
2847 #: rhodecode/templates/base/base.html:313
2770 #: rhodecode/templates/base/base.html:313
2848 #: rhodecode/templates/journal/journal.html:4
2771 #: rhodecode/templates/journal/journal.html:4
2849 #: rhodecode/templates/journal/journal.html:21
2772 #: rhodecode/templates/journal/journal.html:21
2850 #: rhodecode/templates/journal/public_journal.html:4
2773 #: rhodecode/templates/journal/public_journal.html:4
2851 msgid "Journal"
2774 msgid "Journal"
2852 msgstr "日志"
2775 msgstr "日志"
2853
2776
2854 #: rhodecode/templates/base/base.html:125
2777 #: rhodecode/templates/base/base.html:125
2855 msgid "Log Out"
2778 msgid "Log Out"
2856 msgstr "退出"
2779 msgstr "退出"
2857
2780
2858 #: rhodecode/templates/base/base.html:144
2781 #: rhodecode/templates/base/base.html:144
2859 msgid "Switch repository"
2782 msgid "Switch repository"
2860 msgstr "切换版本库"
2783 msgstr "切换版本库"
2861
2784
2862 #: rhodecode/templates/base/base.html:146
2785 #: rhodecode/templates/base/base.html:146
2863 msgid "Products"
2786 msgid "Products"
2864 msgstr ""
2787 msgstr "产品"
2865
2788
2866 #: rhodecode/templates/base/base.html:152
2789 #: rhodecode/templates/base/base.html:152
2867 #: rhodecode/templates/base/base.html:182
2790 #: rhodecode/templates/base/base.html:182
2868 msgid "loading..."
2791 msgid "loading..."
2869 msgstr ""
2792 msgstr "载入中..."
2870
2793
2871 #: rhodecode/templates/base/base.html:158
2794 #: rhodecode/templates/base/base.html:158
2872 #: rhodecode/templates/base/base.html:160
2795 #: rhodecode/templates/base/base.html:160
2873 #: rhodecode/templates/base/base.html:162
2796 #: rhodecode/templates/base/base.html:162
2874 #: rhodecode/templates/data_table/_dt_elements.html:15
2797 #: rhodecode/templates/data_table/_dt_elements.html:15
2875 #: rhodecode/templates/data_table/_dt_elements.html:17
2798 #: rhodecode/templates/data_table/_dt_elements.html:17
2876 #: rhodecode/templates/data_table/_dt_elements.html:19
2799 #: rhodecode/templates/data_table/_dt_elements.html:19
2877 msgid "Summary"
2800 msgid "Summary"
2878 msgstr "概况"
2801 msgstr "概况"
2879
2802
2880 #: rhodecode/templates/base/base.html:166
2803 #: rhodecode/templates/base/base.html:166
2881 #: rhodecode/templates/base/base.html:168
2804 #: rhodecode/templates/base/base.html:168
2882 #: rhodecode/templates/base/base.html:170
2805 #: rhodecode/templates/base/base.html:170
2883 #: rhodecode/templates/changelog/changelog.html:15
2806 #: rhodecode/templates/changelog/changelog.html:15
2884 #: rhodecode/templates/data_table/_dt_elements.html:23
2807 #: rhodecode/templates/data_table/_dt_elements.html:23
2885 #: rhodecode/templates/data_table/_dt_elements.html:25
2808 #: rhodecode/templates/data_table/_dt_elements.html:25
2886 #: rhodecode/templates/data_table/_dt_elements.html:27
2809 #: rhodecode/templates/data_table/_dt_elements.html:27
2887 msgid "Changelog"
2810 msgid "Changelog"
2888 msgstr "修记录"
2811 msgstr "修记录"
2889
2812
2890 #: rhodecode/templates/base/base.html:175
2813 #: rhodecode/templates/base/base.html:175
2891 #: rhodecode/templates/base/base.html:177
2814 #: rhodecode/templates/base/base.html:177
2892 #: rhodecode/templates/base/base.html:179
2815 #: rhodecode/templates/base/base.html:179
2893 msgid "Switch to"
2816 msgid "Switch to"
2894 msgstr "切换到"
2817 msgstr "切换到"
2895
2818
2896 #: rhodecode/templates/base/base.html:186
2819 #: rhodecode/templates/base/base.html:186
2897 #: rhodecode/templates/base/base.html:188
2820 #: rhodecode/templates/base/base.html:188
2898 #: rhodecode/templates/base/base.html:190
2821 #: rhodecode/templates/base/base.html:190
2899 #: rhodecode/templates/data_table/_dt_elements.html:31
2822 #: rhodecode/templates/data_table/_dt_elements.html:31
2900 #: rhodecode/templates/data_table/_dt_elements.html:33
2823 #: rhodecode/templates/data_table/_dt_elements.html:33
2901 #: rhodecode/templates/data_table/_dt_elements.html:35
2824 #: rhodecode/templates/data_table/_dt_elements.html:35
2902 msgid "Files"
2825 msgid "Files"
2903 msgstr "档案"
2826 msgstr "浏览"
2904
2827
2905 #: rhodecode/templates/base/base.html:195
2828 #: rhodecode/templates/base/base.html:195
2906 #: rhodecode/templates/base/base.html:199
2829 #: rhodecode/templates/base/base.html:199
2907 msgid "Options"
2830 msgid "Options"
2908 msgstr "选项"
2831 msgstr "选项"
2909
2832
2910 #: rhodecode/templates/base/base.html:204
2833 #: rhodecode/templates/base/base.html:204
2911 #: rhodecode/templates/base/base.html:206
2834 #: rhodecode/templates/base/base.html:206
2912 #: rhodecode/templates/base/base.html:227
2835 #: rhodecode/templates/base/base.html:227
2913 msgid "settings"
2836 msgid "settings"
2914 msgstr "设置"
2837 msgstr "设置"
2915
2838
2916 #: rhodecode/templates/base/base.html:209
2839 #: rhodecode/templates/base/base.html:209
2917 #: rhodecode/templates/data_table/_dt_elements.html:80
2840 #: rhodecode/templates/data_table/_dt_elements.html:80
2918 #: rhodecode/templates/forks/fork.html:13
2841 #: rhodecode/templates/forks/fork.html:13
2919 msgid "fork"
2842 msgid "fork"
2920 msgstr ""
2843 msgstr "分支"
2921
2844
2922 #: rhodecode/templates/base/base.html:211
2845 #: rhodecode/templates/base/base.html:211
2923 #: rhodecode/templates/changelog/changelog.html:40
2846 #: rhodecode/templates/changelog/changelog.html:40
2924 msgid "Open new pull request"
2847 msgid "Open new pull request"
2925 msgstr ""
2848 msgstr "新建拉取请求"
2926
2849
2927 #: rhodecode/templates/base/base.html:213
2850 #: rhodecode/templates/base/base.html:213
2928 msgid "search"
2851 msgid "search"
2929 msgstr "搜索"
2852 msgstr "搜索"
2930
2853
2931 #: rhodecode/templates/base/base.html:222
2854 #: rhodecode/templates/base/base.html:222
2932 msgid "repositories groups"
2855 msgid "repositories groups"
2933 msgstr "版本库组"
2856 msgstr "版本库组"
2934
2857
2935 #: rhodecode/templates/base/base.html:224
2858 #: rhodecode/templates/base/base.html:224
2936 msgid "users groups"
2859 msgid "users groups"
2937 msgstr "用户组"
2860 msgstr "用户组"
2938
2861
2939 #: rhodecode/templates/base/base.html:225
2862 #: rhodecode/templates/base/base.html:225
2940 msgid "permissions"
2863 msgid "permissions"
2941 msgstr "权限"
2864 msgstr "权限"
2942
2865
2943 #: rhodecode/templates/base/base.html:238
2866 #: rhodecode/templates/base/base.html:238
2944 #: rhodecode/templates/base/base.html:240
2867 #: rhodecode/templates/base/base.html:240
2945 msgid "Followers"
2868 msgid "Followers"
2946 msgstr "跟随者"
2869 msgstr "关注者"
2947
2870
2948 #: rhodecode/templates/base/base.html:246
2871 #: rhodecode/templates/base/base.html:246
2949 #: rhodecode/templates/base/base.html:248
2872 #: rhodecode/templates/base/base.html:248
2950 msgid "Forks"
2873 msgid "Forks"
2951 msgstr ""
2874 msgstr "分支"
2952
2875
2953 #: rhodecode/templates/base/base.html:327
2876 #: rhodecode/templates/base/base.html:327
2954 #: rhodecode/templates/base/base.html:329
2877 #: rhodecode/templates/base/base.html:329
2955 #: rhodecode/templates/base/base.html:331
2878 #: rhodecode/templates/base/base.html:331
2956 #: rhodecode/templates/search/search.html:52
2879 #: rhodecode/templates/search/search.html:52
2957 msgid "Search"
2880 msgid "Search"
2958 msgstr "搜索"
2881 msgstr "搜索"
2959
2882
2960 #: rhodecode/templates/base/root.html:42
2883 #: rhodecode/templates/base/root.html:42
2961 #, fuzzy
2962 msgid "add another comment"
2884 msgid "add another comment"
2963 msgstr "添加成员"
2885 msgstr "添加新的评论"
2964
2886
2965 #: rhodecode/templates/base/root.html:43
2887 #: rhodecode/templates/base/root.html:43
2966 #: rhodecode/templates/journal/journal.html:120
2888 #: rhodecode/templates/journal/journal.html:120
2967 #: rhodecode/templates/summary/summary.html:57
2889 #: rhodecode/templates/summary/summary.html:57
2968 msgid "Stop following this repository"
2890 msgid "Stop following this repository"
2969 msgstr "停止跟随该版本库"
2891 msgstr "停止关注该版本库"
2970
2892
2971 #: rhodecode/templates/base/root.html:44
2893 #: rhodecode/templates/base/root.html:44
2972 #: rhodecode/templates/summary/summary.html:61
2894 #: rhodecode/templates/summary/summary.html:61
2973 msgid "Start following this repository"
2895 msgid "Start following this repository"
2974 msgstr "开始跟随该版本库"
2896 msgstr "开始关注该版本库"
2975
2897
2976 #: rhodecode/templates/base/root.html:45
2898 #: rhodecode/templates/base/root.html:45
2977 msgid "Group"
2899 msgid "Group"
2978 msgstr "组"
2900 msgstr "组"
2979
2901
2980 #: rhodecode/templates/base/root.html:47
2902 #: rhodecode/templates/base/root.html:47
2981 msgid "search truncated"
2903 msgid "search truncated"
2982 msgstr ""
2904 msgstr "搜索被截断"
2983
2905
2984 #: rhodecode/templates/base/root.html:48
2906 #: rhodecode/templates/base/root.html:48
2985 msgid "no matching files"
2907 msgid "no matching files"
2986 msgstr "没有符合的文件"
2908 msgstr "没有符合的文件"
2987
2909
2988 #: rhodecode/templates/bookmarks/bookmarks.html:5
2910 #: rhodecode/templates/bookmarks/bookmarks.html:5
2989 #, python-format
2911 #, python-format
2990 msgid "%s Bookmarks"
2912 msgid "%s Bookmarks"
2991 msgstr ""
2913 msgstr "%s 书签"
2992
2914
2993 #: rhodecode/templates/bookmarks/bookmarks.html:39
2915 #: rhodecode/templates/bookmarks/bookmarks.html:39
2994 #: rhodecode/templates/bookmarks/bookmarks_data.html:8
2916 #: rhodecode/templates/bookmarks/bookmarks_data.html:8
2995 #: rhodecode/templates/branches/branches.html:54
2917 #: rhodecode/templates/branches/branches.html:54
2996 #: rhodecode/templates/tags/tags.html:39
2918 #: rhodecode/templates/tags/tags.html:39
2997 #: rhodecode/templates/tags/tags_data.html:8
2919 #: rhodecode/templates/tags/tags_data.html:8
2998 #, fuzzy
2999 msgid "Author"
2920 msgid "Author"
3000 msgstr "作者"
2921 msgstr "作者"
3001
2922
3002 #: rhodecode/templates/branches/branches.html:5
2923 #: rhodecode/templates/branches/branches.html:5
3003 #, fuzzy, python-format
2924 #, python-format
3004 msgid "%s Branches"
2925 msgid "%s Branches"
3005 msgstr "分支"
2926 msgstr "%s 分支"
3006
2927
3007 #: rhodecode/templates/branches/branches.html:29
2928 #: rhodecode/templates/branches/branches.html:29
3008 #, fuzzy
3009 msgid "Compare branches"
2929 msgid "Compare branches"
3010 msgstr "分支"
2930 msgstr "比较分支"
3011
2931
3012 #: rhodecode/templates/branches/branches.html:57
2932 #: rhodecode/templates/branches/branches.html:57
3013 #: rhodecode/templates/compare/compare_diff.html:5
2933 #: rhodecode/templates/compare/compare_diff.html:5
3014 #: rhodecode/templates/compare/compare_diff.html:13
2934 #: rhodecode/templates/compare/compare_diff.html:13
3015 #, fuzzy
3016 msgid "Compare"
2935 msgid "Compare"
3017 msgstr "比较显示"
2936 msgstr "比较显示"
3018
2937
3019 #: rhodecode/templates/branches/branches_data.html:6
2938 #: rhodecode/templates/branches/branches_data.html:6
3020 msgid "name"
2939 msgid "name"
3021 msgstr "名称"
2940 msgstr "名称"
3022
2941
3023 #: rhodecode/templates/branches/branches_data.html:7
2942 #: rhodecode/templates/branches/branches_data.html:7
3024 msgid "date"
2943 msgid "date"
3025 msgstr "日期"
2944 msgstr "日期"
3026
2945
3027 #: rhodecode/templates/branches/branches_data.html:8
2946 #: rhodecode/templates/branches/branches_data.html:8
3028 #: rhodecode/templates/shortlog/shortlog_data.html:8
2947 #: rhodecode/templates/shortlog/shortlog_data.html:8
3029 msgid "author"
2948 msgid "author"
3030 msgstr "作者"
2949 msgstr "作者"
3031
2950
3032 #: rhodecode/templates/branches/branches_data.html:9
2951 #: rhodecode/templates/branches/branches_data.html:9
3033 #: rhodecode/templates/shortlog/shortlog_data.html:5
2952 #: rhodecode/templates/shortlog/shortlog_data.html:5
3034 msgid "revision"
2953 msgid "revision"
3035 msgstr "修订"
2954 msgstr "修订"
3036
2955
3037 #: rhodecode/templates/branches/branches_data.html:10
2956 #: rhodecode/templates/branches/branches_data.html:10
3038 #, fuzzy
3039 msgid "compare"
2957 msgid "compare"
3040 msgstr "比较显示"
2958 msgstr "比较显示"
3041
2959
3042 #: rhodecode/templates/changelog/changelog.html:6
2960 #: rhodecode/templates/changelog/changelog.html:6
3043 #, fuzzy, python-format
2961 #, python-format
3044 msgid "%s Changelog"
2962 msgid "%s Changelog"
3045 msgstr "修改记录"
2963 msgstr "%s 修订记录"
3046
2964
3047 #: rhodecode/templates/changelog/changelog.html:15
2965 #: rhodecode/templates/changelog/changelog.html:15
3048 #, python-format
2966 #, python-format
3049 msgid "showing %d out of %d revision"
2967 msgid "showing %d out of %d revision"
3050 msgid_plural "showing %d out of %d revisions"
2968 msgid_plural "showing %d out of %d revisions"
3051 msgstr[0] ""
2969 msgstr[0] "显示 %2d 中的 %1d 个版本"
3052
2970
3053 #: rhodecode/templates/changelog/changelog.html:37
2971 #: rhodecode/templates/changelog/changelog.html:37
3054 #: rhodecode/templates/forks/forks_data.html:19
2972 #: rhodecode/templates/forks/forks_data.html:19
3055 #, python-format
2973 #, python-format
3056 msgid "compare fork with %s"
2974 msgid "compare fork with %s"
3057 msgstr ""
2975 msgstr "与 %s 比较"
3058
2976
3059 #: rhodecode/templates/changelog/changelog.html:37
2977 #: rhodecode/templates/changelog/changelog.html:37
3060 #: rhodecode/templates/forks/forks_data.html:21
2978 #: rhodecode/templates/forks/forks_data.html:21
3061 msgid "Compare fork"
2979 msgid "Compare fork"
3062 msgstr ""
2980 msgstr "比较分支"
3063
2981
3064 #: rhodecode/templates/changelog/changelog.html:46
2982 #: rhodecode/templates/changelog/changelog.html:46
3065 msgid "Show"
2983 msgid "Show"
3066 msgstr "显示"
2984 msgstr "显示"
3067
2985
3068 #: rhodecode/templates/changelog/changelog.html:72
2986 #: rhodecode/templates/changelog/changelog.html:72
3069 #: rhodecode/templates/summary/summary.html:364
2987 #: rhodecode/templates/summary/summary.html:364
3070 msgid "show more"
2988 msgid "show more"
3071 msgstr ""
2989 msgstr "显示更多"
3072
2990
3073 #: rhodecode/templates/changelog/changelog.html:76
2991 #: rhodecode/templates/changelog/changelog.html:76
3074 msgid "Affected number of files, click to show more details"
2992 msgid "Affected number of files, click to show more details"
3075 msgstr ""
2993 msgstr "影响的文件数,点击显示详细信息"
3076
2994
3077 #: rhodecode/templates/changelog/changelog.html:89
2995 #: rhodecode/templates/changelog/changelog.html:89
3078 #: rhodecode/templates/changeset/changeset.html:38
2996 #: rhodecode/templates/changeset/changeset.html:38
3079 #: rhodecode/templates/changeset/changeset_file_comment.html:20
2997 #: rhodecode/templates/changeset/changeset_file_comment.html:20
3080 #: rhodecode/templates/changeset/changeset_range.html:46
2998 #: rhodecode/templates/changeset/changeset_range.html:46
3081 #, fuzzy
3082 msgid "Changeset status"
2999 msgid "Changeset status"
3083 msgstr "变更集"
3000 msgstr "修订集状态"
3084
3001
3085 #: rhodecode/templates/changelog/changelog.html:92
3002 #: rhodecode/templates/changelog/changelog.html:92
3086 msgid "Click to open associated pull request"
3003 msgid "Click to open associated pull request"
3087 msgstr ""
3004 msgstr "点击建立相关的拉取请求"
3088
3005
3089 #: rhodecode/templates/changelog/changelog.html:102
3006 #: rhodecode/templates/changelog/changelog.html:102
3090 #: rhodecode/templates/changeset/changeset.html:78
3007 #: rhodecode/templates/changeset/changeset.html:78
3091 msgid "Parent"
3008 msgid "Parent"
3092 msgstr ""
3009 msgstr "父版本"
3093
3010
3094 #: rhodecode/templates/changelog/changelog.html:108
3011 #: rhodecode/templates/changelog/changelog.html:108
3095 #: rhodecode/templates/changeset/changeset.html:84
3012 #: rhodecode/templates/changeset/changeset.html:84
3096 msgid "No parents"
3013 msgid "No parents"
3097 msgstr ""
3014 msgstr "无父版本"
3098
3015
3099 #: rhodecode/templates/changelog/changelog.html:113
3016 #: rhodecode/templates/changelog/changelog.html:113
3100 #: rhodecode/templates/changeset/changeset.html:88
3017 #: rhodecode/templates/changeset/changeset.html:88
3101 msgid "merge"
3018 msgid "merge"
3102 msgstr "合并"
3019 msgstr "合并"
3103
3020
3104 #: rhodecode/templates/changelog/changelog.html:116
3021 #: rhodecode/templates/changelog/changelog.html:116
3105 #: rhodecode/templates/changeset/changeset.html:91
3022 #: rhodecode/templates/changeset/changeset.html:91
3106 #: rhodecode/templates/files/files.html:29
3023 #: rhodecode/templates/files/files.html:29
3107 #: rhodecode/templates/files/files_add.html:33
3024 #: rhodecode/templates/files/files_add.html:33
3108 #: rhodecode/templates/files/files_edit.html:33
3025 #: rhodecode/templates/files/files_edit.html:33
3109 #: rhodecode/templates/shortlog/shortlog_data.html:9
3026 #: rhodecode/templates/shortlog/shortlog_data.html:9
3110 msgid "branch"
3027 msgid "branch"
3111 msgstr "分支"
3028 msgstr "分支"
3112
3029
3113 #: rhodecode/templates/changelog/changelog.html:122
3030 #: rhodecode/templates/changelog/changelog.html:122
3114 msgid "bookmark"
3031 msgid "bookmark"
3115 msgstr ""
3032 msgstr "书签"
3116
3033
3117 #: rhodecode/templates/changelog/changelog.html:128
3034 #: rhodecode/templates/changelog/changelog.html:128
3118 #: rhodecode/templates/changeset/changeset.html:96
3035 #: rhodecode/templates/changeset/changeset.html:96
3119 msgid "tag"
3036 msgid "tag"
3120 msgstr "标签"
3037 msgstr "标签"
3121
3038
3122 #: rhodecode/templates/changelog/changelog.html:164
3039 #: rhodecode/templates/changelog/changelog.html:164
3123 msgid "Show selected changes __S -> __E"
3040 msgid "Show selected changes __S -> __E"
3124 msgstr ""
3041 msgstr "显示选定的修订集 __S -> __E"
3125
3042
3126 #: rhodecode/templates/changelog/changelog.html:255
3043 #: rhodecode/templates/changelog/changelog.html:255
3127 msgid "There are no changes yet"
3044 msgid "There are no changes yet"
3128 msgstr "没有任何变更"
3045 msgstr "没有任何变更"
3129
3046
3130 #: rhodecode/templates/changelog/changelog_details.html:4
3047 #: rhodecode/templates/changelog/changelog_details.html:4
3131 #: rhodecode/templates/changeset/changeset.html:66
3048 #: rhodecode/templates/changeset/changeset.html:66
3132 msgid "removed"
3049 msgid "removed"
3133 msgstr "移除"
3050 msgstr "移除"
3134
3051
3135 #: rhodecode/templates/changelog/changelog_details.html:5
3052 #: rhodecode/templates/changelog/changelog_details.html:5
3136 #: rhodecode/templates/changeset/changeset.html:67
3053 #: rhodecode/templates/changeset/changeset.html:67
3137 msgid "changed"
3054 msgid "changed"
3138 msgstr "修改"
3055 msgstr "修改"
3139
3056
3140 #: rhodecode/templates/changelog/changelog_details.html:6
3057 #: rhodecode/templates/changelog/changelog_details.html:6
3141 #: rhodecode/templates/changeset/changeset.html:68
3058 #: rhodecode/templates/changeset/changeset.html:68
3142 msgid "added"
3059 msgid "added"
3143 msgstr "添加"
3060 msgstr "添加"
3144
3061
3145 #: rhodecode/templates/changelog/changelog_details.html:8
3062 #: rhodecode/templates/changelog/changelog_details.html:8
3146 #: rhodecode/templates/changelog/changelog_details.html:9
3063 #: rhodecode/templates/changelog/changelog_details.html:9
3147 #: rhodecode/templates/changelog/changelog_details.html:10
3064 #: rhodecode/templates/changelog/changelog_details.html:10
3148 #: rhodecode/templates/changeset/changeset.html:70
3065 #: rhodecode/templates/changeset/changeset.html:70
3149 #: rhodecode/templates/changeset/changeset.html:71
3066 #: rhodecode/templates/changeset/changeset.html:71
3150 #: rhodecode/templates/changeset/changeset.html:72
3067 #: rhodecode/templates/changeset/changeset.html:72
3151 #, python-format
3068 #, python-format
3152 msgid "affected %s files"
3069 msgid "affected %s files"
3153 msgstr ""
3070 msgstr "影响 %s 文件"
3154
3071
3155 #: rhodecode/templates/changeset/changeset.html:6
3072 #: rhodecode/templates/changeset/changeset.html:6
3156 #, fuzzy, python-format
3073 #, python-format
3157 msgid "%s Changeset"
3074 msgid "%s Changeset"
3158 msgstr "无变更"
3075 msgstr "%s 修订集"
3159
3076
3160 #: rhodecode/templates/changeset/changeset.html:14
3077 #: rhodecode/templates/changeset/changeset.html:14
3161 msgid "Changeset"
3078 msgid "Changeset"
3162 msgstr ""
3079 msgstr "修订集"
3163
3080
3164 #: rhodecode/templates/changeset/changeset.html:43
3081 #: rhodecode/templates/changeset/changeset.html:43
3165 #: rhodecode/templates/changeset/diff_block.html:20
3082 #: rhodecode/templates/changeset/diff_block.html:20
3166 msgid "raw diff"
3083 msgid "raw diff"
3167 msgstr "原始 diff"
3084 msgstr "原始 diff"
3168
3085
3169 #: rhodecode/templates/changeset/changeset.html:44
3086 #: rhodecode/templates/changeset/changeset.html:44
3170 #: rhodecode/templates/changeset/diff_block.html:21
3087 #: rhodecode/templates/changeset/diff_block.html:21
3171 msgid "download diff"
3088 msgid "download diff"
3172 msgstr "下载 diff"
3089 msgstr "下载 diff"
3173
3090
3174 #: rhodecode/templates/changeset/changeset.html:48
3091 #: rhodecode/templates/changeset/changeset.html:48
3175 #: rhodecode/templates/changeset/changeset_file_comment.html:82
3092 #: rhodecode/templates/changeset/changeset_file_comment.html:82
3176 #, fuzzy, python-format
3093 #, python-format
3177 msgid "%d comment"
3094 msgid "%d comment"
3178 msgid_plural "%d comments"
3095 msgid_plural "%d comments"
3179 msgstr[0] "提交"
3096 msgstr[0] "%d 条评论"
3180
3097
3181 #: rhodecode/templates/changeset/changeset.html:48
3098 #: rhodecode/templates/changeset/changeset.html:48
3182 #: rhodecode/templates/changeset/changeset_file_comment.html:82
3099 #: rhodecode/templates/changeset/changeset_file_comment.html:82
3183 #, python-format
3100 #, python-format
3184 msgid "(%d inline)"
3101 msgid "(%d inline)"
3185 msgid_plural "(%d inline)"
3102 msgid_plural "(%d inline)"
3186 msgstr[0] ""
3103 msgstr[0] "(%d 内嵌)"
3187
3104
3188 #: rhodecode/templates/changeset/changeset.html:103
3105 #: rhodecode/templates/changeset/changeset.html:103
3189 #, python-format
3106 #, python-format
3190 msgid "%s files affected with %s insertions and %s deletions:"
3107 msgid "%s files affected with %s insertions and %s deletions:"
3191 msgstr ""
3108 msgstr "%s 个文件受影响包括 %s 行插入和 %s 行删除:"
3192
3109
3193 #: rhodecode/templates/changeset/changeset.html:119
3110 #: rhodecode/templates/changeset/changeset.html:119
3194 msgid "Changeset was too big and was cut off..."
3111 msgid "Changeset was too big and was cut off..."
3195 msgstr ""
3112 msgstr "修订集太大已被截断......"
3196
3113
3197 #: rhodecode/templates/changeset/changeset_file_comment.html:42
3114 #: rhodecode/templates/changeset/changeset_file_comment.html:42
3198 msgid "Submitting..."
3115 msgid "Submitting..."
3199 msgstr ""
3116 msgstr "提交中……"
3200
3117
3201 #: rhodecode/templates/changeset/changeset_file_comment.html:45
3118 #: rhodecode/templates/changeset/changeset_file_comment.html:45
3202 msgid "Commenting on line {1}."
3119 msgid "Commenting on line {1}."
3203 msgstr ""
3120 msgstr "在 {1} 行上评论"
3204
3121
3205 #: rhodecode/templates/changeset/changeset_file_comment.html:46
3122 #: rhodecode/templates/changeset/changeset_file_comment.html:46
3206 #: rhodecode/templates/changeset/changeset_file_comment.html:121
3123 #: rhodecode/templates/changeset/changeset_file_comment.html:121
3207 #, python-format
3124 #, python-format
3208 msgid "Comments parsed using %s syntax with %s support."
3125 msgid "Comments parsed using %s syntax with %s support."
3209 msgstr ""
3126 msgstr "评论使用 %s 语法并支持 %s"
3210
3127
3211 #: rhodecode/templates/changeset/changeset_file_comment.html:48
3128 #: rhodecode/templates/changeset/changeset_file_comment.html:48
3212 #: rhodecode/templates/changeset/changeset_file_comment.html:123
3129 #: rhodecode/templates/changeset/changeset_file_comment.html:123
3213 msgid "Use @username inside this text to send notification to this RhodeCode user"
3130 msgid ""
3214 msgstr ""
3131 "Use @username inside this text to send notification to this RhodeCode user"
3132 msgstr "在文本中使用 @用户名 以发送通知到该 RhodeCode 用户"
3215
3133
3216 #: rhodecode/templates/changeset/changeset_file_comment.html:59
3134 #: rhodecode/templates/changeset/changeset_file_comment.html:59
3217 #: rhodecode/templates/changeset/changeset_file_comment.html:138
3135 #: rhodecode/templates/changeset/changeset_file_comment.html:138
3218 #, fuzzy
3219 msgid "Comment"
3136 msgid "Comment"
3220 msgstr "提交"
3137 msgstr "评论"
3221
3138
3222 #: rhodecode/templates/changeset/changeset_file_comment.html:60
3139 #: rhodecode/templates/changeset/changeset_file_comment.html:60
3223 #: rhodecode/templates/changeset/changeset_file_comment.html:71
3140 #: rhodecode/templates/changeset/changeset_file_comment.html:71
3224 msgid "Hide"
3141 msgid "Hide"
3225 msgstr ""
3142 msgstr "影藏"
3226
3143
3227 #: rhodecode/templates/changeset/changeset_file_comment.html:67
3144 #: rhodecode/templates/changeset/changeset_file_comment.html:67
3228 #, fuzzy
3229 msgid "You need to be logged in to comment."
3145 msgid "You need to be logged in to comment."
3230 msgstr "必须登录才能访问该页面"
3146 msgstr "必须登录才能评论"
3231
3147
3232 #: rhodecode/templates/changeset/changeset_file_comment.html:67
3148 #: rhodecode/templates/changeset/changeset_file_comment.html:67
3233 msgid "Login now"
3149 msgid "Login now"
3234 msgstr ""
3150 msgstr "现在登陆"
3235
3151
3236 #: rhodecode/templates/changeset/changeset_file_comment.html:118
3152 #: rhodecode/templates/changeset/changeset_file_comment.html:118
3237 msgid "Leave a comment"
3153 msgid "Leave a comment"
3238 msgstr ""
3154 msgstr "发表评论"
3239
3155
3240 #: rhodecode/templates/changeset/changeset_file_comment.html:124
3156 #: rhodecode/templates/changeset/changeset_file_comment.html:124
3241 msgid "Check this to change current status of code-review for this changeset"
3157 msgid "Check this to change current status of code-review for this changeset"
3242 msgstr ""
3158 msgstr "勾选以改变这个修订集的代码审查状态"
3243
3159
3244 #: rhodecode/templates/changeset/changeset_file_comment.html:124
3160 #: rhodecode/templates/changeset/changeset_file_comment.html:124
3245 #, fuzzy
3246 msgid "change status"
3161 msgid "change status"
3247 msgstr "变更集"
3162 msgstr "改变状态"
3248
3163
3249 #: rhodecode/templates/changeset/changeset_file_comment.html:140
3164 #: rhodecode/templates/changeset/changeset_file_comment.html:140
3250 msgid "Comment and close"
3165 msgid "Comment and close"
3251 msgstr ""
3166 msgstr "评论并关闭"
3252
3167
3253 #: rhodecode/templates/changeset/changeset_range.html:5
3168 #: rhodecode/templates/changeset/changeset_range.html:5
3254 #, fuzzy, python-format
3169 #, python-format
3255 msgid "%s Changesets"
3170 msgid "%s Changesets"
3256 msgstr "变更集"
3171 msgstr "%s 修订集"
3257
3172
3258 #: rhodecode/templates/changeset/changeset_range.html:29
3173 #: rhodecode/templates/changeset/changeset_range.html:29
3259 #: rhodecode/templates/compare/compare_diff.html:29
3174 #: rhodecode/templates/compare/compare_diff.html:29
3260 msgid "Compare View"
3175 msgid "Compare View"
3261 msgstr "比较显示"
3176 msgstr "比较显示"
3262
3177
3263 #: rhodecode/templates/changeset/changeset_range.html:54
3178 #: rhodecode/templates/changeset/changeset_range.html:54
3264 #: rhodecode/templates/compare/compare_diff.html:41
3179 #: rhodecode/templates/compare/compare_diff.html:41
3265 #: rhodecode/templates/pullrequests/pullrequest_show.html:69
3180 #: rhodecode/templates/pullrequests/pullrequest_show.html:69
3266 msgid "Files affected"
3181 msgid "Files affected"
3267 msgstr ""
3182 msgstr "影响文件"
3268
3183
3269 #: rhodecode/templates/changeset/diff_block.html:19
3184 #: rhodecode/templates/changeset/diff_block.html:19
3270 msgid "diff"
3185 msgid "diff"
3271 msgstr ""
3186 msgstr "差别"
3272
3187
3273 #: rhodecode/templates/changeset/diff_block.html:27
3188 #: rhodecode/templates/changeset/diff_block.html:27
3274 #, fuzzy
3275 msgid "show inline comments"
3189 msgid "show inline comments"
3276 msgstr "文件内容"
3190 msgstr "显示内嵌评论"
3277
3191
3278 #: rhodecode/templates/compare/compare_cs.html:5
3192 #: rhodecode/templates/compare/compare_cs.html:5
3279 #, fuzzy
3280 msgid "No changesets"
3193 msgid "No changesets"
3281 msgstr "无修订"
3194 msgstr "无修订"
3282
3195
3283 #: rhodecode/templates/compare/compare_diff.html:37
3196 #: rhodecode/templates/compare/compare_diff.html:37
3284 #, fuzzy
3285 msgid "Outgoing changesets"
3197 msgid "Outgoing changesets"
3286 msgstr "尚无修订"
3198 msgstr "传出修订集"
3287
3199
3288 #: rhodecode/templates/data_table/_dt_elements.html:39
3200 #: rhodecode/templates/data_table/_dt_elements.html:39
3289 #: rhodecode/templates/data_table/_dt_elements.html:41
3201 #: rhodecode/templates/data_table/_dt_elements.html:41
3290 #: rhodecode/templates/data_table/_dt_elements.html:43
3202 #: rhodecode/templates/data_table/_dt_elements.html:43
3291 msgid "Fork"
3203 msgid "Fork"
3292 msgstr "分支"
3204 msgstr "分支"
3293
3205
3294 #: rhodecode/templates/data_table/_dt_elements.html:60
3206 #: rhodecode/templates/data_table/_dt_elements.html:60
3295 #: rhodecode/templates/journal/journal.html:126
3207 #: rhodecode/templates/journal/journal.html:126
3296 #: rhodecode/templates/summary/summary.html:68
3208 #: rhodecode/templates/summary/summary.html:68
3297 msgid "Mercurial repository"
3209 msgid "Mercurial repository"
3298 msgstr "Mercurial 版本库"
3210 msgstr "Mercurial 版本库"
3299
3211
3300 #: rhodecode/templates/data_table/_dt_elements.html:62
3212 #: rhodecode/templates/data_table/_dt_elements.html:62
3301 #: rhodecode/templates/journal/journal.html:128
3213 #: rhodecode/templates/journal/journal.html:128
3302 #: rhodecode/templates/summary/summary.html:71
3214 #: rhodecode/templates/summary/summary.html:71
3303 msgid "Git repository"
3215 msgid "Git repository"
3304 msgstr "Git 版本库"
3216 msgstr "Git 版本库"
3305
3217
3306 #: rhodecode/templates/data_table/_dt_elements.html:69
3218 #: rhodecode/templates/data_table/_dt_elements.html:69
3307 #: rhodecode/templates/journal/journal.html:134
3219 #: rhodecode/templates/journal/journal.html:134
3308 #: rhodecode/templates/summary/summary.html:78
3220 #: rhodecode/templates/summary/summary.html:78
3309 msgid "public repository"
3221 msgid "public repository"
3310 msgstr "公共版本库"
3222 msgstr "公共版本库"
3311
3223
3312 #: rhodecode/templates/data_table/_dt_elements.html:80
3224 #: rhodecode/templates/data_table/_dt_elements.html:80
3313 #: rhodecode/templates/summary/summary.html:87
3225 #: rhodecode/templates/summary/summary.html:87
3314 #: rhodecode/templates/summary/summary.html:88
3226 #: rhodecode/templates/summary/summary.html:88
3315 msgid "Fork of"
3227 msgid "Fork of"
3316 msgstr ""
3228 msgstr "分支自"
3317
3229
3318 #: rhodecode/templates/data_table/_dt_elements.html:92
3230 #: rhodecode/templates/data_table/_dt_elements.html:92
3319 msgid "No changesets yet"
3231 msgid "No changesets yet"
3320 msgstr "无修订"
3232 msgstr "无修订"
3321
3233
3322 #: rhodecode/templates/data_table/_dt_elements.html:104
3234 #: rhodecode/templates/data_table/_dt_elements.html:104
3323 #, fuzzy, python-format
3235 #, python-format
3324 msgid "Confirm to delete this user: %s"
3236 msgid "Confirm to delete this user: %s"
3325 msgstr "确认删除用户"
3237 msgstr "确认删除用户: %s"
3326
3238
3327 #: rhodecode/templates/email_templates/main.html:8
3239 #: rhodecode/templates/email_templates/main.html:8
3328 msgid "This is an notification from RhodeCode."
3240 msgid "This is an notification from RhodeCode."
3329 msgstr ""
3241 msgstr "这是 RhodeCode 发送的一个通知。"
3330
3242
3331 #: rhodecode/templates/errors/error_document.html:46
3243 #: rhodecode/templates/errors/error_document.html:46
3332 #, python-format
3244 #, python-format
3333 msgid "You will be redirected to %s in %s seconds"
3245 msgid "You will be redirected to %s in %s seconds"
3334 msgstr ""
3246 msgstr "%1s 秒后你将重定向到 %2s"
3335
3247
3336 #: rhodecode/templates/files/file_diff.html:4
3248 #: rhodecode/templates/files/file_diff.html:4
3337 #, fuzzy, python-format
3249 #, python-format
3338 msgid "%s File diff"
3250 msgid "%s File diff"
3339 msgstr "文件 diff"
3251 msgstr "%s 文件差异"
3340
3252
3341 #: rhodecode/templates/files/file_diff.html:12
3253 #: rhodecode/templates/files/file_diff.html:12
3342 msgid "File diff"
3254 msgid "File diff"
3343 msgstr "文件 diff"
3255 msgstr "文件差异"
3344
3256
3345 #: rhodecode/templates/files/files.html:4
3257 #: rhodecode/templates/files/files.html:4
3346 #: rhodecode/templates/files/files.html:72
3258 #: rhodecode/templates/files/files.html:72
3347 #, fuzzy, python-format
3259 #, python-format
3348 msgid "%s files"
3260 msgid "%s files"
3349 msgstr "文件"
3261 msgstr "%s 文件"
3350
3262
3351 #: rhodecode/templates/files/files.html:12
3263 #: rhodecode/templates/files/files.html:12
3352 #: rhodecode/templates/summary/summary.html:340
3264 #: rhodecode/templates/summary/summary.html:340
3353 msgid "files"
3265 msgid "files"
3354 msgstr "文件"
3266 msgstr "文件"
3355
3267
3356 #: rhodecode/templates/files/files_add.html:4
3268 #: rhodecode/templates/files/files_add.html:4
3357 #: rhodecode/templates/files/files_edit.html:4
3269 #: rhodecode/templates/files/files_edit.html:4
3358 #, fuzzy, python-format
3270 #, python-format
3359 msgid "%s Edit file"
3271 msgid "%s Edit file"
3360 msgstr "编辑文件"
3272 msgstr "%s 编辑文件"
3361
3273
3362 #: rhodecode/templates/files/files_add.html:19
3274 #: rhodecode/templates/files/files_add.html:19
3363 #, fuzzy
3364 msgid "add file"
3275 msgid "add file"
3365 msgstr "编辑文件"
3276 msgstr "新建文件"
3366
3277
3367 #: rhodecode/templates/files/files_add.html:40
3278 #: rhodecode/templates/files/files_add.html:40
3368 #, fuzzy
3369 msgid "Add new file"
3279 msgid "Add new file"
3370 msgstr "添加新用户"
3280 msgstr "新建文件"
3371
3281
3372 #: rhodecode/templates/files/files_add.html:45
3282 #: rhodecode/templates/files/files_add.html:45
3373 #, fuzzy
3374 msgid "File Name"
3283 msgid "File Name"
3375 msgstr "文件名"
3284 msgstr "文件名"
3376
3285
3377 #: rhodecode/templates/files/files_add.html:49
3286 #: rhodecode/templates/files/files_add.html:49
3378 #: rhodecode/templates/files/files_add.html:58
3287 #: rhodecode/templates/files/files_add.html:58
3379 #, fuzzy
3380 msgid "or"
3288 msgid "or"
3381 msgstr ""
3289 msgstr "或者"
3382
3290
3383 #: rhodecode/templates/files/files_add.html:49
3291 #: rhodecode/templates/files/files_add.html:49
3384 #: rhodecode/templates/files/files_add.html:54
3292 #: rhodecode/templates/files/files_add.html:54
3385 #, fuzzy
3386 msgid "Upload file"
3293 msgid "Upload file"
3387 msgstr "编辑文件"
3294 msgstr "上传文件"
3388
3295
3389 #: rhodecode/templates/files/files_add.html:58
3296 #: rhodecode/templates/files/files_add.html:58
3390 #, fuzzy
3391 msgid "Create new file"
3297 msgid "Create new file"
3392 msgstr "创建用户 %s"
3298 msgstr "创建新文件"
3393
3299
3394 #: rhodecode/templates/files/files_add.html:63
3300 #: rhodecode/templates/files/files_add.html:63
3395 #: rhodecode/templates/files/files_edit.html:39
3301 #: rhodecode/templates/files/files_edit.html:39
3396 #: rhodecode/templates/files/files_ypjax.html:3
3302 #: rhodecode/templates/files/files_ypjax.html:3
3397 msgid "Location"
3303 msgid "Location"
3398 msgstr "位置"
3304 msgstr "位置"
3399
3305
3400 #: rhodecode/templates/files/files_add.html:67
3306 #: rhodecode/templates/files/files_add.html:67
3401 msgid "use / to separate directories"
3307 msgid "use / to separate directories"
3402 msgstr ""
3308 msgstr "使用 / 目录分隔符"
3403
3309
3404 #: rhodecode/templates/files/files_add.html:77
3310 #: rhodecode/templates/files/files_add.html:77
3405 #: rhodecode/templates/files/files_edit.html:63
3311 #: rhodecode/templates/files/files_edit.html:63
3406 #: rhodecode/templates/shortlog/shortlog_data.html:6
3312 #: rhodecode/templates/shortlog/shortlog_data.html:6
3407 msgid "commit message"
3313 msgid "commit message"
3408 msgstr "提交信息"
3314 msgstr "提交信息"
3409
3315
3410 #: rhodecode/templates/files/files_add.html:81
3316 #: rhodecode/templates/files/files_add.html:81
3411 #: rhodecode/templates/files/files_edit.html:67
3317 #: rhodecode/templates/files/files_edit.html:67
3412 msgid "Commit changes"
3318 msgid "Commit changes"
3413 msgstr "提交修改"
3319 msgstr "提交修改"
3414
3320
3415 #: rhodecode/templates/files/files_browser.html:13
3321 #: rhodecode/templates/files/files_browser.html:13
3416 msgid "view"
3322 msgid "view"
3417 msgstr "显示"
3323 msgstr "显示"
3418
3324
3419 #: rhodecode/templates/files/files_browser.html:14
3325 #: rhodecode/templates/files/files_browser.html:14
3420 msgid "previous revision"
3326 msgid "previous revision"
3421 msgstr "上一个修订"
3327 msgstr "上一个修订"
3422
3328
3423 #: rhodecode/templates/files/files_browser.html:16
3329 #: rhodecode/templates/files/files_browser.html:16
3424 msgid "next revision"
3330 msgid "next revision"
3425 msgstr "下一个修订"
3331 msgstr "下一个修订"
3426
3332
3427 #: rhodecode/templates/files/files_browser.html:23
3333 #: rhodecode/templates/files/files_browser.html:23
3428 msgid "follow current branch"
3334 msgid "follow current branch"
3429 msgstr ""
3335 msgstr "沿着当前分支"
3430
3336
3431 #: rhodecode/templates/files/files_browser.html:27
3337 #: rhodecode/templates/files/files_browser.html:27
3432 msgid "search file list"
3338 msgid "search file list"
3433 msgstr "搜索文件列表"
3339 msgstr "搜索文件列表"
3434
3340
3435 #: rhodecode/templates/files/files_browser.html:31
3341 #: rhodecode/templates/files/files_browser.html:31
3436 #: rhodecode/templates/shortlog/shortlog_data.html:65
3342 #: rhodecode/templates/shortlog/shortlog_data.html:65
3437 #, fuzzy
3438 msgid "add new file"
3343 msgid "add new file"
3439 msgstr "添加新用户"
3344 msgstr "新建文件"
3440
3345
3441 #: rhodecode/templates/files/files_browser.html:35
3346 #: rhodecode/templates/files/files_browser.html:35
3442 msgid "Loading file list..."
3347 msgid "Loading file list..."
3443 msgstr "加载文件列表..."
3348 msgstr "加载文件列表..."
3444
3349
3445 #: rhodecode/templates/files/files_browser.html:48
3350 #: rhodecode/templates/files/files_browser.html:48
3446 msgid "Size"
3351 msgid "Size"
3447 msgstr "大小"
3352 msgstr "大小"
3448
3353
3449 #: rhodecode/templates/files/files_browser.html:49
3354 #: rhodecode/templates/files/files_browser.html:49
3450 msgid "Mimetype"
3355 msgid "Mimetype"
3451 msgstr ""
3356 msgstr "MIME 类型"
3452
3357
3453 #: rhodecode/templates/files/files_browser.html:50
3358 #: rhodecode/templates/files/files_browser.html:50
3454 #, fuzzy
3455 msgid "Last Revision"
3359 msgid "Last Revision"
3456 msgstr "下一个修订"
3360 msgstr "最后修订号"
3457
3361
3458 #: rhodecode/templates/files/files_browser.html:51
3362 #: rhodecode/templates/files/files_browser.html:51
3459 msgid "Last modified"
3363 msgid "Last modified"
3460 msgstr "最后修改"
3364 msgstr "最后修改"
3461
3365
3462 #: rhodecode/templates/files/files_browser.html:52
3366 #: rhodecode/templates/files/files_browser.html:52
3463 msgid "Last commiter"
3367 msgid "Last commiter"
3464 msgstr "最后提交"
3368 msgstr "最后提交"
3465
3369
3466 #: rhodecode/templates/files/files_edit.html:19
3370 #: rhodecode/templates/files/files_edit.html:19
3467 msgid "edit file"
3371 msgid "edit file"
3468 msgstr "编辑文件"
3372 msgstr "编辑文件"
3469
3373
3470 #: rhodecode/templates/files/files_edit.html:49
3374 #: rhodecode/templates/files/files_edit.html:49
3471 #: rhodecode/templates/files/files_source.html:38
3375 #: rhodecode/templates/files/files_source.html:38
3472 msgid "show annotation"
3376 msgid "show annotation"
3473 msgstr "显示注释"
3377 msgstr "显示注释"
3474
3378
3475 #: rhodecode/templates/files/files_edit.html:50
3379 #: rhodecode/templates/files/files_edit.html:50
3476 #: rhodecode/templates/files/files_source.html:40
3380 #: rhodecode/templates/files/files_source.html:40
3477 #: rhodecode/templates/files/files_source.html:68
3381 #: rhodecode/templates/files/files_source.html:68
3478 msgid "show as raw"
3382 msgid "show as raw"
3479 msgstr "显示原始文件"
3383 msgstr "显示原始文件"
3480
3384
3481 #: rhodecode/templates/files/files_edit.html:51
3385 #: rhodecode/templates/files/files_edit.html:51
3482 #: rhodecode/templates/files/files_source.html:41
3386 #: rhodecode/templates/files/files_source.html:41
3483 msgid "download as raw"
3387 msgid "download as raw"
3484 msgstr "下载原始文件"
3388 msgstr "下载原始文件"
3485
3389
3486 #: rhodecode/templates/files/files_edit.html:54
3390 #: rhodecode/templates/files/files_edit.html:54
3487 #, fuzzy
3488 msgid "source"
3391 msgid "source"
3489 msgstr "显示代码"
3392 msgstr "显示源文件"
3490
3393
3491 #: rhodecode/templates/files/files_edit.html:59
3394 #: rhodecode/templates/files/files_edit.html:59
3492 #, fuzzy
3493 msgid "Editing file"
3395 msgid "Editing file"
3494 msgstr "编辑文件"
3396 msgstr "编辑文件"
3495
3397
3496 #: rhodecode/templates/files/files_source.html:2
3398 #: rhodecode/templates/files/files_source.html:2
3497 msgid "History"
3399 msgid "History"
3498 msgstr "历史"
3400 msgstr "历史"
3499
3401
3500 #: rhodecode/templates/files/files_source.html:9
3402 #: rhodecode/templates/files/files_source.html:9
3501 #, fuzzy
3502 msgid "diff to revision"
3403 msgid "diff to revision"
3503 msgstr "下一个修订"
3404 msgstr "比较差异"
3504
3405
3505 #: rhodecode/templates/files/files_source.html:10
3406 #: rhodecode/templates/files/files_source.html:10
3506 #, fuzzy
3507 msgid "show at revision"
3407 msgid "show at revision"
3508 msgstr "下一个修订"
3408 msgstr "显示修订"
3509
3409
3510 #: rhodecode/templates/files/files_source.html:14
3410 #: rhodecode/templates/files/files_source.html:14
3511 #, fuzzy, python-format
3411 #, python-format
3512 msgid "%s author"
3412 msgid "%s author"
3513 msgid_plural "%s authors"
3413 msgid_plural "%s authors"
3514 msgstr[0] "作者"
3414 msgstr[0] "%s 个作者"
3515
3415
3516 #: rhodecode/templates/files/files_source.html:36
3416 #: rhodecode/templates/files/files_source.html:36
3517 msgid "show source"
3417 msgid "show source"
3518 msgstr "显示代码"
3418 msgstr "显示代码"
3519
3419
3520 #: rhodecode/templates/files/files_source.html:59
3420 #: rhodecode/templates/files/files_source.html:59
3521 #, python-format
3421 #, python-format
3522 msgid "Binary file (%s)"
3422 msgid "Binary file (%s)"
3523 msgstr "二进制文件(%s)"
3423 msgstr "二进制文件(%s)"
3524
3424
3525 #: rhodecode/templates/files/files_source.html:68
3425 #: rhodecode/templates/files/files_source.html:68
3526 msgid "File is too big to display"
3426 msgid "File is too big to display"
3527 msgstr "文件过大,不能显示"
3427 msgstr "文件过大,不能显示"
3528
3428
3529 #: rhodecode/templates/files/files_source.html:124
3429 #: rhodecode/templates/files/files_source.html:124
3530 msgid "Selection link"
3430 msgid "Selection link"
3531 msgstr ""
3431 msgstr "选择链接"
3532
3432
3533 #: rhodecode/templates/files/files_ypjax.html:5
3433 #: rhodecode/templates/files/files_ypjax.html:5
3534 #, fuzzy
3535 msgid "annotation"
3434 msgid "annotation"
3536 msgstr "显示注释"
3435 msgstr "显示注释"
3537
3436
3538 #: rhodecode/templates/files/files_ypjax.html:15
3437 #: rhodecode/templates/files/files_ypjax.html:15
3539 msgid "Go back"
3438 msgid "Go back"
3540 msgstr ""
3439 msgstr "返回"
3541
3440
3542 #: rhodecode/templates/files/files_ypjax.html:16
3441 #: rhodecode/templates/files/files_ypjax.html:16
3543 msgid "No files at given path"
3442 msgid "No files at given path"
3544 msgstr ""
3443 msgstr "指定的路径中没有文件"
3545
3444
3546 #: rhodecode/templates/followers/followers.html:5
3445 #: rhodecode/templates/followers/followers.html:5
3547 #, fuzzy, python-format
3446 #, python-format
3548 msgid "%s Followers"
3447 msgid "%s Followers"
3549 msgstr "跟随者"
3448 msgstr "%s 个关注者"
3550
3449
3551 #: rhodecode/templates/followers/followers.html:13
3450 #: rhodecode/templates/followers/followers.html:13
3552 msgid "followers"
3451 msgid "followers"
3553 msgstr "跟随者"
3452 msgstr "关注者"
3554
3453
3555 #: rhodecode/templates/followers/followers_data.html:12
3454 #: rhodecode/templates/followers/followers_data.html:12
3556 #, fuzzy
3557 msgid "Started following -"
3455 msgid "Started following -"
3558 msgstr "开始跟随"
3456 msgstr "开始关注 - "
3559
3457
3560 #: rhodecode/templates/forks/fork.html:5
3458 #: rhodecode/templates/forks/fork.html:5
3561 #, fuzzy, python-format
3459 #, python-format
3562 msgid "%s Fork"
3460 msgid "%s Fork"
3563 msgstr "分支"
3461 msgstr "%s 的分支"
3564
3462
3565 #: rhodecode/templates/forks/fork.html:31
3463 #: rhodecode/templates/forks/fork.html:31
3566 msgid "Fork name"
3464 msgid "Fork name"
3567 msgstr "分支名"
3465 msgstr "分支名"
3568
3466
3569 #: rhodecode/templates/forks/fork.html:68
3467 #: rhodecode/templates/forks/fork.html:68
3570 msgid "Private"
3468 msgid "Private"
3571 msgstr "私有"
3469 msgstr "私有"
3572
3470
3573 #: rhodecode/templates/forks/fork.html:77
3471 #: rhodecode/templates/forks/fork.html:77
3574 #, fuzzy
3575 msgid "Copy permissions"
3472 msgid "Copy permissions"
3576 msgstr "权限"
3473 msgstr "拷贝权限"
3577
3474
3578 #: rhodecode/templates/forks/fork.html:81
3475 #: rhodecode/templates/forks/fork.html:81
3579 msgid "Copy permissions from forked repository"
3476 msgid "Copy permissions from forked repository"
3580 msgstr ""
3477 msgstr "从被分支版本库拷贝权限"
3581
3478
3582 #: rhodecode/templates/forks/fork.html:86
3479 #: rhodecode/templates/forks/fork.html:86
3583 msgid "Update after clone"
3480 msgid "Update after clone"
3584 msgstr ""
3481 msgstr "克隆后更新"
3585
3482
3586 #: rhodecode/templates/forks/fork.html:90
3483 #: rhodecode/templates/forks/fork.html:90
3587 msgid "Checkout source after making a clone"
3484 msgid "Checkout source after making a clone"
3588 msgstr ""
3485 msgstr "完成克隆后检出源代码"
3589
3486
3590 #: rhodecode/templates/forks/fork.html:94
3487 #: rhodecode/templates/forks/fork.html:94
3591 msgid "fork this repository"
3488 msgid "fork this repository"
3592 msgstr "对该版本库建立分支"
3489 msgstr "对该版本库建立分支"
3593
3490
3594 #: rhodecode/templates/forks/forks.html:5
3491 #: rhodecode/templates/forks/forks.html:5
3595 #, fuzzy, python-format
3492 #, python-format
3596 msgid "%s Forks"
3493 msgid "%s Forks"
3597 msgstr "分支"
3494 msgstr "%s 的分支"
3598
3495
3599 #: rhodecode/templates/forks/forks.html:13
3496 #: rhodecode/templates/forks/forks.html:13
3600 msgid "forks"
3497 msgid "forks"
3601 msgstr "分支"
3498 msgstr "分支"
3602
3499
3603 #: rhodecode/templates/forks/forks_data.html:17
3500 #: rhodecode/templates/forks/forks_data.html:17
3604 msgid "forked"
3501 msgid "forked"
3605 msgstr "已有分支"
3502 msgstr "已有分支"
3606
3503
3607 #: rhodecode/templates/forks/forks_data.html:38
3504 #: rhodecode/templates/forks/forks_data.html:38
3608 msgid "There are no forks yet"
3505 msgid "There are no forks yet"
3609 msgstr "尚未有任何分支"
3506 msgstr "分支"
3610
3507
3611 #: rhodecode/templates/journal/journal.html:13
3508 #: rhodecode/templates/journal/journal.html:13
3612 #, fuzzy
3613 msgid "ATOM journal feed"
3509 msgid "ATOM journal feed"
3614 msgstr "公共日志 %s %s 订阅"
3510 msgstr "订阅日志 ATOM"
3615
3511
3616 #: rhodecode/templates/journal/journal.html:14
3512 #: rhodecode/templates/journal/journal.html:14
3617 #, fuzzy
3618 msgid "RSS journal feed"
3513 msgid "RSS journal feed"
3619 msgstr "公共日志 %s %s 订阅"
3514 msgstr "订阅日志 RSS"
3620
3515
3621 #: rhodecode/templates/journal/journal.html:24
3516 #: rhodecode/templates/journal/journal.html:24
3622 #: rhodecode/templates/pullrequests/pullrequest.html:27
3517 #: rhodecode/templates/pullrequests/pullrequest.html:27
3623 msgid "Refresh"
3518 msgid "Refresh"
3624 msgstr ""
3519 msgstr "刷新"
3625
3520
3626 #: rhodecode/templates/journal/journal.html:27
3521 #: rhodecode/templates/journal/journal.html:27
3627 #: rhodecode/templates/journal/public_journal.html:24
3522 #: rhodecode/templates/journal/public_journal.html:24
3628 #, fuzzy
3629 msgid "RSS feed"
3523 msgid "RSS feed"
3630 msgstr "%s %s 订阅"
3524 msgstr "订阅 RSS"
3631
3525
3632 #: rhodecode/templates/journal/journal.html:30
3526 #: rhodecode/templates/journal/journal.html:30
3633 #: rhodecode/templates/journal/public_journal.html:27
3527 #: rhodecode/templates/journal/public_journal.html:27
3634 msgid "ATOM feed"
3528 msgid "ATOM feed"
3635 msgstr ""
3529 msgstr "订阅 ATOM"
3636
3530
3637 #: rhodecode/templates/journal/journal.html:41
3531 #: rhodecode/templates/journal/journal.html:41
3638 #, fuzzy
3639 msgid "Watched"
3532 msgid "Watched"
3640 msgstr "缓存"
3533 msgstr "关注的"
3641
3534
3642 #: rhodecode/templates/journal/journal.html:46
3535 #: rhodecode/templates/journal/journal.html:46
3643 #, fuzzy
3644 msgid "ADD"
3536 msgid "ADD"
3645 msgstr "新"
3537 msgstr "新建版本库"
3646
3538
3647 #: rhodecode/templates/journal/journal.html:114
3539 #: rhodecode/templates/journal/journal.html:114
3648 msgid "following user"
3540 msgid "following user"
3649 msgstr "跟随中用户"
3541 msgstr "关注用户"
3650
3542
3651 #: rhodecode/templates/journal/journal.html:114
3543 #: rhodecode/templates/journal/journal.html:114
3652 msgid "user"
3544 msgid "user"
3653 msgstr "用户"
3545 msgstr "用户"
3654
3546
3655 #: rhodecode/templates/journal/journal.html:147
3547 #: rhodecode/templates/journal/journal.html:147
3656 msgid "You are not following any users or repositories"
3548 msgid "You are not following any users or repositories"
3657 msgstr "尚未跟随任何用户或版本库"
3549 msgstr "未关注任何用户或版本库"
3658
3550
3659 #: rhodecode/templates/journal/journal_data.html:47
3551 #: rhodecode/templates/journal/journal_data.html:47
3660 msgid "No entries yet"
3552 msgid "No entries yet"
3661 msgstr ""
3553 msgstr "没有条目"
3662
3554
3663 #: rhodecode/templates/journal/public_journal.html:13
3555 #: rhodecode/templates/journal/public_journal.html:13
3664 #, fuzzy
3665 msgid "ATOM public journal feed"
3556 msgid "ATOM public journal feed"
3666 msgstr "公共日志 %s %s 订阅"
3557 msgstr "订阅公共日志 ATOM"
3667
3558
3668 #: rhodecode/templates/journal/public_journal.html:14
3559 #: rhodecode/templates/journal/public_journal.html:14
3669 #, fuzzy
3670 msgid "RSS public journal feed"
3560 msgid "RSS public journal feed"
3671 msgstr "公共日志 %s %s 订阅"
3561 msgstr "订阅公共日志 RSS"
3672
3562
3673 #: rhodecode/templates/journal/public_journal.html:21
3563 #: rhodecode/templates/journal/public_journal.html:21
3674 msgid "Public Journal"
3564 msgid "Public Journal"
3675 msgstr "公共日志"
3565 msgstr "公共日志"
3676
3566
3677 #: rhodecode/templates/pullrequests/pullrequest.html:4
3567 #: rhodecode/templates/pullrequests/pullrequest.html:4
3678 #: rhodecode/templates/pullrequests/pullrequest.html:12
3568 #: rhodecode/templates/pullrequests/pullrequest.html:12
3679 msgid "New pull request"
3569 msgid "New pull request"
3680 msgstr ""
3570 msgstr "新建拉取请求"
3681
3571
3682 #: rhodecode/templates/pullrequests/pullrequest.html:28
3572 #: rhodecode/templates/pullrequests/pullrequest.html:28
3683 msgid "refresh overview"
3573 msgid "refresh overview"
3684 msgstr ""
3574 msgstr "刷新概览"
3685
3575
3686 #: rhodecode/templates/pullrequests/pullrequest.html:66
3576 #: rhodecode/templates/pullrequests/pullrequest.html:66
3687 #, fuzzy
3688 msgid "Detailed compare view"
3577 msgid "Detailed compare view"
3689 msgstr "比较显示"
3578 msgstr "详细比较显示"
3690
3579
3691 #: rhodecode/templates/pullrequests/pullrequest.html:70
3580 #: rhodecode/templates/pullrequests/pullrequest.html:70
3692 #: rhodecode/templates/pullrequests/pullrequest_show.html:82
3581 #: rhodecode/templates/pullrequests/pullrequest_show.html:82
3693 msgid "Pull request reviewers"
3582 msgid "Pull request reviewers"
3694 msgstr ""
3583 msgstr "拉取请求检视人员"
3695
3584
3696 #: rhodecode/templates/pullrequests/pullrequest.html:79
3585 #: rhodecode/templates/pullrequests/pullrequest.html:79
3697 #: rhodecode/templates/pullrequests/pullrequest_show.html:94
3586 #: rhodecode/templates/pullrequests/pullrequest_show.html:94
3698 #, fuzzy
3699 msgid "owner"
3587 msgid "owner"
3700 msgstr "所有者"
3588 msgstr "所有者"
3701
3589
3702 #: rhodecode/templates/pullrequests/pullrequest.html:91
3590 #: rhodecode/templates/pullrequests/pullrequest.html:91
3703 #: rhodecode/templates/pullrequests/pullrequest_show.html:109
3591 #: rhodecode/templates/pullrequests/pullrequest_show.html:109
3704 msgid "Add reviewer to this pull request."
3592 msgid "Add reviewer to this pull request."
3705 msgstr ""
3593 msgstr "为这个拉取请求增加检视人员"
3706
3594
3707 #: rhodecode/templates/pullrequests/pullrequest.html:97
3595 #: rhodecode/templates/pullrequests/pullrequest.html:97
3708 #, fuzzy
3709 msgid "Create new pull request"
3596 msgid "Create new pull request"
3710 msgstr "创建用户 %s"
3597 msgstr "创建新的拉取请求"
3711
3598
3712 #: rhodecode/templates/pullrequests/pullrequest.html:106
3599 #: rhodecode/templates/pullrequests/pullrequest.html:106
3713 #: rhodecode/templates/pullrequests/pullrequest_show.html:25
3600 #: rhodecode/templates/pullrequests/pullrequest_show.html:25
3714 #: rhodecode/templates/pullrequests/pullrequest_show_all.html:33
3601 #: rhodecode/templates/pullrequests/pullrequest_show_all.html:33
3715 #, fuzzy
3716 msgid "Title"
3602 msgid "Title"
3717 msgstr ""
3603 msgstr "标题"
3718
3604
3719 #: rhodecode/templates/pullrequests/pullrequest.html:115
3605 #: rhodecode/templates/pullrequests/pullrequest.html:115
3720 #, fuzzy
3721 msgid "description"
3606 msgid "description"
3722 msgstr "描述"
3607 msgstr "描述"
3723
3608
3724 #: rhodecode/templates/pullrequests/pullrequest.html:123
3609 #: rhodecode/templates/pullrequests/pullrequest.html:123
3725 msgid "Send pull request"
3610 msgid "Send pull request"
3726 msgstr ""
3611 msgstr "发送拉取请求"
3727
3612
3728 #: rhodecode/templates/pullrequests/pullrequest_show.html:23
3613 #: rhodecode/templates/pullrequests/pullrequest_show.html:23
3729 #, python-format
3614 #, python-format
3730 msgid "Closed %s"
3615 msgid "Closed %s"
3731 msgstr ""
3616 msgstr "关闭于 %s"
3732
3617
3733 #: rhodecode/templates/pullrequests/pullrequest_show.html:31
3618 #: rhodecode/templates/pullrequests/pullrequest_show.html:31
3734 #, fuzzy
3735 msgid "Status"
3619 msgid "Status"
3736 msgstr "变更集"
3620 msgstr "状态"
3737
3621
3738 #: rhodecode/templates/pullrequests/pullrequest_show.html:36
3622 #: rhodecode/templates/pullrequests/pullrequest_show.html:36
3739 msgid "Pull request status"
3623 msgid "Pull request status"
3740 msgstr ""
3624 msgstr "拉取请求状态"
3741
3625
3742 #: rhodecode/templates/pullrequests/pullrequest_show.html:44
3626 #: rhodecode/templates/pullrequests/pullrequest_show.html:44
3743 msgid "Still not reviewed by"
3627 msgid "Still not reviewed by"
3744 msgstr ""
3628 msgstr "还未检视的检视人员"
3745
3629
3746 #: rhodecode/templates/pullrequests/pullrequest_show.html:47
3630 #: rhodecode/templates/pullrequests/pullrequest_show.html:47
3747 #, python-format
3631 #, python-format
3748 msgid "%d reviewer"
3632 msgid "%d reviewer"
3749 msgid_plural "%d reviewers"
3633 msgid_plural "%d reviewers"
3750 msgstr[0] ""
3634 msgstr[0] "%d 个检视者"
3751
3635
3752 #: rhodecode/templates/pullrequests/pullrequest_show.html:54
3636 #: rhodecode/templates/pullrequests/pullrequest_show.html:54
3753 #, fuzzy
3754 msgid "Created on"
3637 msgid "Created on"
3755 msgstr "创建用户 %s"
3638 msgstr "创建 %s"
3756
3639
3757 #: rhodecode/templates/pullrequests/pullrequest_show.html:61
3640 #: rhodecode/templates/pullrequests/pullrequest_show.html:61
3758 #, fuzzy
3759 msgid "Compare view"
3641 msgid "Compare view"
3760 msgstr "比较显示"
3642 msgstr "比较显示"
3761
3643
3762 #: rhodecode/templates/pullrequests/pullrequest_show.html:65
3644 #: rhodecode/templates/pullrequests/pullrequest_show.html:65
3763 #, fuzzy
3764 msgid "Incoming changesets"
3645 msgid "Incoming changesets"
3765 msgstr "尚无修订"
3646 msgstr "传入修订集"
3766
3647
3767 #: rhodecode/templates/pullrequests/pullrequest_show_all.html:4
3648 #: rhodecode/templates/pullrequests/pullrequest_show_all.html:4
3768 #, fuzzy
3769 msgid "all pull requests"
3649 msgid "all pull requests"
3770 msgstr "创建用户 %s"
3650 msgstr "所有拉取请求"
3771
3651
3772 #: rhodecode/templates/pullrequests/pullrequest_show_all.html:12
3652 #: rhodecode/templates/pullrequests/pullrequest_show_all.html:12
3773 msgid "All pull requests"
3653 msgid "All pull requests"
3774 msgstr ""
3654 msgstr "所有拉取请求"
3775
3655
3776 #: rhodecode/templates/pullrequests/pullrequest_show_all.html:27
3656 #: rhodecode/templates/pullrequests/pullrequest_show_all.html:27
3777 msgid "Closed"
3657 msgid "Closed"
3778 msgstr ""
3658 msgstr "已关闭"
3779
3659
3660 # 中文中 repo name 在前面 serch term在后面
3780 #: rhodecode/templates/search/search.html:6
3661 #: rhodecode/templates/search/search.html:6
3781 #, fuzzy, python-format
3662 #, python-format
3782 msgid "Search \"%s\" in repository: %s"
3663 msgid "Search \"%s\" in repository: %s"
3783 msgstr "在版本库"
3664 msgstr "在版本库 %2s 中搜索 \"%1s\""
3784
3665
3785 #: rhodecode/templates/search/search.html:8
3666 #: rhodecode/templates/search/search.html:8
3786 #, fuzzy, python-format
3667 #, python-format
3787 msgid "Search \"%s\" in all repositories"
3668 msgid "Search \"%s\" in all repositories"
3788 msgstr "在所有的版本库"
3669 msgstr "在所有的版本库中搜索 \"%s\""
3789
3670
3790 #: rhodecode/templates/search/search.html:12
3671 #: rhodecode/templates/search/search.html:12
3791 #: rhodecode/templates/search/search.html:32
3672 #: rhodecode/templates/search/search.html:32
3792 #, fuzzy, python-format
3673 #, python-format
3793 msgid "Search in repository: %s"
3674 msgid "Search in repository: %s"
3794 msgstr "在版本库"
3675 msgstr "在版本库 %s 中搜索"
3795
3676
3796 #: rhodecode/templates/search/search.html:14
3677 #: rhodecode/templates/search/search.html:14
3797 #: rhodecode/templates/search/search.html:34
3678 #: rhodecode/templates/search/search.html:34
3798 #, fuzzy
3799 msgid "Search in all repositories"
3679 msgid "Search in all repositories"
3800 msgstr "在所有的版本库"
3680 msgstr "在所有的版本库中搜索"
3801
3681
3802 #: rhodecode/templates/search/search.html:48
3682 #: rhodecode/templates/search/search.html:48
3803 msgid "Search term"
3683 msgid "Search term"
3804 msgstr "搜索短语"
3684 msgstr "搜索短语"
3805
3685
3806 #: rhodecode/templates/search/search.html:60
3686 #: rhodecode/templates/search/search.html:60
3807 msgid "Search in"
3687 msgid "Search in"
3808 msgstr "搜索范围"
3688 msgstr "搜索范围"
3809
3689
3810 #: rhodecode/templates/search/search.html:63
3690 #: rhodecode/templates/search/search.html:63
3811 msgid "File contents"
3691 msgid "File contents"
3812 msgstr "文件内容"
3692 msgstr "文件内容"
3813
3693
3814 #: rhodecode/templates/search/search.html:64
3694 #: rhodecode/templates/search/search.html:64
3815 #, fuzzy
3816 msgid "Commit messages"
3695 msgid "Commit messages"
3817 msgstr "提交信息"
3696 msgstr "提交信息"
3818
3697
3819 #: rhodecode/templates/search/search.html:65
3698 #: rhodecode/templates/search/search.html:65
3820 msgid "File names"
3699 msgid "File names"
3821 msgstr "文件名"
3700 msgstr "文件名"
3822
3701
3823 #: rhodecode/templates/search/search_commit.html:35
3702 #: rhodecode/templates/search/search_commit.html:35
3824 #: rhodecode/templates/search/search_content.html:21
3703 #: rhodecode/templates/search/search_content.html:21
3825 #: rhodecode/templates/search/search_path.html:15
3704 #: rhodecode/templates/search/search_path.html:15
3826 msgid "Permission denied"
3705 msgid "Permission denied"
3827 msgstr "权限不足"
3706 msgstr "权限不足"
3828
3707
3829 #: rhodecode/templates/settings/repo_settings.html:5
3708 #: rhodecode/templates/settings/repo_settings.html:5
3830 #, fuzzy, python-format
3709 #, python-format
3831 msgid "%s Settings"
3710 msgid "%s Settings"
3832 msgstr "设置"
3711 msgstr "%s 设置"
3833
3712
3834 #: rhodecode/templates/shortlog/shortlog.html:5
3713 #: rhodecode/templates/shortlog/shortlog.html:5
3835 #, fuzzy, python-format
3714 #, python-format
3836 msgid "%s Shortlog"
3715 msgid "%s Shortlog"
3837 msgstr "简短日志"
3716 msgstr "%s 简短日志"
3838
3717
3839 #: rhodecode/templates/shortlog/shortlog.html:14
3718 #: rhodecode/templates/shortlog/shortlog.html:14
3840 msgid "shortlog"
3719 msgid "shortlog"
3841 msgstr "简短日志"
3720 msgstr "简短日志"
3842
3721
3843 #: rhodecode/templates/shortlog/shortlog_data.html:7
3722 #: rhodecode/templates/shortlog/shortlog_data.html:7
3844 msgid "age"
3723 msgid "age"
3845 msgstr ""
3724 msgstr "时间"
3846
3725
3847 #: rhodecode/templates/shortlog/shortlog_data.html:18
3726 #: rhodecode/templates/shortlog/shortlog_data.html:18
3848 #, fuzzy
3849 msgid "No commit message"
3727 msgid "No commit message"
3850 msgstr "提交信息"
3728 msgstr "没有提交信息"
3851
3729
3852 #: rhodecode/templates/shortlog/shortlog_data.html:62
3730 #: rhodecode/templates/shortlog/shortlog_data.html:62
3853 msgid "Add or upload files directly via RhodeCode"
3731 msgid "Add or upload files directly via RhodeCode"
3854 msgstr ""
3732 msgstr "通过 RhodeCode 直接添加或者上传文件"
3855
3733
3856 #: rhodecode/templates/shortlog/shortlog_data.html:71
3734 #: rhodecode/templates/shortlog/shortlog_data.html:71
3857 msgid "Push new repo"
3735 msgid "Push new repo"
3858 msgstr ""
3736 msgstr "Push 新版本库"
3859
3737
3860 #: rhodecode/templates/shortlog/shortlog_data.html:79
3738 #: rhodecode/templates/shortlog/shortlog_data.html:79
3861 #, fuzzy
3862 msgid "Existing repository?"
3739 msgid "Existing repository?"
3863 msgstr "Git 版本库"
3740 msgstr "现有版本库?"
3864
3741
3865 #: rhodecode/templates/summary/summary.html:4
3742 #: rhodecode/templates/summary/summary.html:4
3866 #, fuzzy, python-format
3743 #, python-format
3867 msgid "%s Summary"
3744 msgid "%s Summary"
3868 msgstr "概要"
3745 msgstr "%s 概要"
3869
3746
3870 #: rhodecode/templates/summary/summary.html:12
3747 #: rhodecode/templates/summary/summary.html:12
3871 msgid "summary"
3748 msgid "summary"
3872 msgstr "概要"
3749 msgstr "概要"
3873
3750
3874 #: rhodecode/templates/summary/summary.html:20
3751 #: rhodecode/templates/summary/summary.html:20
3875 #, fuzzy, python-format
3752 #, python-format
3876 msgid "repo %s ATOM feed"
3753 msgid "repo %s ATOM feed"
3877 msgstr "订阅 atom %s"
3754 msgstr "订阅 ATOM %s"
3878
3755
3879 #: rhodecode/templates/summary/summary.html:21
3756 #: rhodecode/templates/summary/summary.html:21
3880 #, fuzzy, python-format
3757 #, python-format
3881 msgid "repo %s RSS feed"
3758 msgid "repo %s RSS feed"
3882 msgstr "订阅 rss %s"
3759 msgstr "订阅 RSS %s"
3883
3760
3884 #: rhodecode/templates/summary/summary.html:49
3761 #: rhodecode/templates/summary/summary.html:49
3885 #: rhodecode/templates/summary/summary.html:52
3762 #: rhodecode/templates/summary/summary.html:52
3886 #, fuzzy
3887 msgid "ATOM"
3763 msgid "ATOM"
3888 msgstr "作者"
3764 msgstr "ATOM"
3889
3765
3890 #: rhodecode/templates/summary/summary.html:82
3766 #: rhodecode/templates/summary/summary.html:82
3891 #, fuzzy, python-format
3767 #, python-format
3892 msgid "Non changable ID %s"
3768 msgid "Non changable ID %s"
3893 msgstr "无变更"
3769 msgstr "不可变 ID %s"
3894
3770
3895 #: rhodecode/templates/summary/summary.html:87
3771 #: rhodecode/templates/summary/summary.html:87
3896 msgid "public"
3772 msgid "public"
3897 msgstr "公共"
3773 msgstr "公共"
3898
3774
3899 #: rhodecode/templates/summary/summary.html:95
3775 #: rhodecode/templates/summary/summary.html:95
3900 msgid "remote clone"
3776 msgid "remote clone"
3901 msgstr "远程 clone"
3777 msgstr "远程克隆"
3902
3778
3903 #: rhodecode/templates/summary/summary.html:116
3779 #: rhodecode/templates/summary/summary.html:116
3904 msgid "Contact"
3780 msgid "Contact"
3905 msgstr "联系方式"
3781 msgstr "联系方式"
3906
3782
3907 #: rhodecode/templates/summary/summary.html:130
3783 #: rhodecode/templates/summary/summary.html:130
3908 msgid "Clone url"
3784 msgid "Clone url"
3909 msgstr "clone 地址"
3785 msgstr "克隆地址"
3910
3786
3911 #: rhodecode/templates/summary/summary.html:133
3787 #: rhodecode/templates/summary/summary.html:133
3912 msgid "Show by Name"
3788 msgid "Show by Name"
3913 msgstr ""
3789 msgstr "以名字显示"
3914
3790
3915 #: rhodecode/templates/summary/summary.html:134
3791 #: rhodecode/templates/summary/summary.html:134
3916 msgid "Show by ID"
3792 msgid "Show by ID"
3917 msgstr ""
3793 msgstr "以 ID 显示"
3918
3794
3919 #: rhodecode/templates/summary/summary.html:142
3795 #: rhodecode/templates/summary/summary.html:142
3920 #, fuzzy
3921 msgid "Trending files"
3796 msgid "Trending files"
3922 msgstr "编辑文件"
3797 msgstr "文件趋势图"
3923
3798
3924 #: rhodecode/templates/summary/summary.html:150
3799 #: rhodecode/templates/summary/summary.html:150
3925 #: rhodecode/templates/summary/summary.html:166
3800 #: rhodecode/templates/summary/summary.html:166
3926 #: rhodecode/templates/summary/summary.html:194
3801 #: rhodecode/templates/summary/summary.html:194
3927 msgid "enable"
3802 msgid "enable"
3928 msgstr "启用"
3803 msgstr "启用"
3929
3804
3930 #: rhodecode/templates/summary/summary.html:158
3805 #: rhodecode/templates/summary/summary.html:158
3931 msgid "Download"
3806 msgid "Download"
3932 msgstr "下载"
3807 msgstr "下载"
3933
3808
3934 #: rhodecode/templates/summary/summary.html:162
3809 #: rhodecode/templates/summary/summary.html:162
3935 msgid "There are no downloads yet"
3810 msgid "There are no downloads yet"
3936 msgstr "任何下载"
3811 msgstr "无下载"
3937
3812
3938 #: rhodecode/templates/summary/summary.html:164
3813 #: rhodecode/templates/summary/summary.html:164
3939 msgid "Downloads are disabled for this repository"
3814 msgid "Downloads are disabled for this repository"
3940 msgstr "这个版本库的下载已经禁用"
3815 msgstr "这个版本库的下载已经禁用"
3941
3816
3942 #: rhodecode/templates/summary/summary.html:170
3817 #: rhodecode/templates/summary/summary.html:170
3943 #, fuzzy
3944 msgid "Download as zip"
3818 msgid "Download as zip"
3945 msgstr "下载原始文件"
3819 msgstr "下载 zip 包"
3946
3820
3947 #: rhodecode/templates/summary/summary.html:173
3821 #: rhodecode/templates/summary/summary.html:173
3948 msgid "Check this to download archive with subrepos"
3822 msgid "Check this to download archive with subrepos"
3949 msgstr ""
3823 msgstr "勾选以下载包含子版本库的压缩包"
3950
3824
3951 #: rhodecode/templates/summary/summary.html:173
3825 #: rhodecode/templates/summary/summary.html:173
3952 msgid "with subrepos"
3826 msgid "with subrepos"
3953 msgstr ""
3827 msgstr "包括子版本库"
3954
3828
3955 #: rhodecode/templates/summary/summary.html:186
3829 #: rhodecode/templates/summary/summary.html:186
3956 msgid "Commit activity by day / author"
3830 msgid "Commit activity by day / author"
3957 msgstr ""
3831 msgstr "按日期或作者的提交活动"
3958
3832
3959 #: rhodecode/templates/summary/summary.html:197
3833 #: rhodecode/templates/summary/summary.html:197
3960 msgid "Stats gathered: "
3834 msgid "Stats gathered: "
3961 msgstr ""
3835 msgstr "已收集的统计: "
3962
3836
3963 #: rhodecode/templates/summary/summary.html:218
3837 #: rhodecode/templates/summary/summary.html:218
3964 msgid "Shortlog"
3838 msgid "Shortlog"
3965 msgstr "简短日志"
3839 msgstr "简短日志"
3966
3840
3967 #: rhodecode/templates/summary/summary.html:220
3841 #: rhodecode/templates/summary/summary.html:220
3968 #, fuzzy
3969 msgid "Quick start"
3842 msgid "Quick start"
3970 msgstr "快速过滤..."
3843 msgstr "快速入门"
3971
3844
3972 #: rhodecode/templates/summary/summary.html:233
3845 #: rhodecode/templates/summary/summary.html:233
3973 #, python-format
3846 #, python-format
3974 msgid "Readme file at revision '%s'"
3847 msgid "Readme file at revision '%s'"
3975 msgstr ""
3848 msgstr "修订 '%s' 中的README"
3976
3849
3977 #: rhodecode/templates/summary/summary.html:236
3850 #: rhodecode/templates/summary/summary.html:236
3978 msgid "Permalink to this readme"
3851 msgid "Permalink to this readme"
3979 msgstr ""
3852 msgstr "这个 README 的固定链接"
3980
3853
3981 #: rhodecode/templates/summary/summary.html:293
3854 #: rhodecode/templates/summary/summary.html:293
3982 #, python-format
3855 #, python-format
3983 msgid "Download %s as %s"
3856 msgid "Download %s as %s"
3984 msgstr "下载 %s 作为 %s"
3857 msgstr "下载 %s 作为 %s"
3985
3858
3986 #: rhodecode/templates/summary/summary.html:650
3859 #: rhodecode/templates/summary/summary.html:650
3987 msgid "commits"
3860 msgid "commits"
3988 msgstr "提交"
3861 msgstr "提交"
3989
3862
3990 #: rhodecode/templates/summary/summary.html:651
3863 #: rhodecode/templates/summary/summary.html:651
3991 msgid "files added"
3864 msgid "files added"
3992 msgstr "文件已添加"
3865 msgstr "文件已添加"
3993
3866
3994 #: rhodecode/templates/summary/summary.html:652
3867 #: rhodecode/templates/summary/summary.html:652
3995 msgid "files changed"
3868 msgid "files changed"
3996 msgstr "文件已更改"
3869 msgstr "文件已更改"
3997
3870
3998 #: rhodecode/templates/summary/summary.html:653
3871 #: rhodecode/templates/summary/summary.html:653
3999 msgid "files removed"
3872 msgid "files removed"
4000 msgstr "文件已删除"
3873 msgstr "文件已删除"
4001
3874
4002 #: rhodecode/templates/summary/summary.html:656
3875 #: rhodecode/templates/summary/summary.html:656
4003 msgid "commit"
3876 msgid "commit"
4004 msgstr "提交"
3877 msgstr "提交"
4005
3878
4006 #: rhodecode/templates/summary/summary.html:657
3879 #: rhodecode/templates/summary/summary.html:657
4007 msgid "file added"
3880 msgid "file added"
4008 msgstr "文件已添加"
3881 msgstr "文件已添加"
4009
3882
4010 #: rhodecode/templates/summary/summary.html:658
3883 #: rhodecode/templates/summary/summary.html:658
4011 msgid "file changed"
3884 msgid "file changed"
4012 msgstr "文件已更改"
3885 msgstr "文件已更改"
4013
3886
4014 #: rhodecode/templates/summary/summary.html:659
3887 #: rhodecode/templates/summary/summary.html:659
4015 msgid "file removed"
3888 msgid "file removed"
4016 msgstr "文件已删除"
3889 msgstr "文件已删除"
4017
3890
4018 #: rhodecode/templates/tags/tags.html:5
3891 #: rhodecode/templates/tags/tags.html:5
4019 #, fuzzy, python-format
3892 #, python-format
4020 msgid "%s Tags"
3893 msgid "%s Tags"
4021 msgstr "之前"
3894 msgstr "%s 标签"
4022
@@ -1,636 +1,637 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.diffs
3 rhodecode.lib.diffs
4 ~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~
5
5
6 Set of diffing helpers, previously part of vcs
6 Set of diffing helpers, previously part of vcs
7
7
8
8
9 :created_on: Dec 4, 2011
9 :created_on: Dec 4, 2011
10 :author: marcink
10 :author: marcink
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
12 :original copyright: 2007-2008 by Armin Ronacher
12 :original copyright: 2007-2008 by Armin Ronacher
13 :license: GPLv3, see COPYING for more details.
13 :license: GPLv3, see COPYING for more details.
14 """
14 """
15 # This program is free software: you can redistribute it and/or modify
15 # This program is free software: you can redistribute it and/or modify
16 # it under the terms of the GNU General Public License as published by
16 # it under the terms of the GNU General Public License as published by
17 # the Free Software Foundation, either version 3 of the License, or
17 # the Free Software Foundation, either version 3 of the License, or
18 # (at your option) any later version.
18 # (at your option) any later version.
19 #
19 #
20 # This program is distributed in the hope that it will be useful,
20 # This program is distributed in the hope that it will be useful,
21 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # but WITHOUT ANY WARRANTY; without even the implied warranty of
22 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 # GNU General Public License for more details.
23 # GNU General Public License for more details.
24 #
24 #
25 # You should have received a copy of the GNU General Public License
25 # You should have received a copy of the GNU General Public License
26 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 # along with this program. If not, see <http://www.gnu.org/licenses/>.
27
27
28 import re
28 import re
29 import difflib
29 import difflib
30 import markupsafe
30 import markupsafe
31
31
32 from itertools import tee, imap
32 from itertools import tee, imap
33
33
34 from mercurial import patch
34 from mercurial import patch
35 from mercurial.mdiff import diffopts
35 from mercurial.mdiff import diffopts
36 from mercurial.bundlerepo import bundlerepository
36 from mercurial.bundlerepo import bundlerepository
37
37
38 from pylons.i18n.translation import _
38 from pylons.i18n.translation import _
39
39
40 from rhodecode.lib.compat import BytesIO
40 from rhodecode.lib.compat import BytesIO
41 from rhodecode.lib.vcs.utils.hgcompat import localrepo
41 from rhodecode.lib.vcs.utils.hgcompat import localrepo
42 from rhodecode.lib.vcs.exceptions import VCSError
42 from rhodecode.lib.vcs.exceptions import VCSError
43 from rhodecode.lib.vcs.nodes import FileNode, SubModuleNode
43 from rhodecode.lib.vcs.nodes import FileNode, SubModuleNode
44 from rhodecode.lib.vcs.backends.base import EmptyChangeset
44 from rhodecode.lib.vcs.backends.base import EmptyChangeset
45 from rhodecode.lib.helpers import escape
45 from rhodecode.lib.helpers import escape
46 from rhodecode.lib.utils import make_ui
46 from rhodecode.lib.utils import make_ui
47 from rhodecode.lib.utils2 import safe_unicode
47
48
48
49
49 def wrap_to_table(str_):
50 def wrap_to_table(str_):
50 return '''<table class="code-difftable">
51 return '''<table class="code-difftable">
51 <tr class="line no-comment">
52 <tr class="line no-comment">
52 <td class="lineno new"></td>
53 <td class="lineno new"></td>
53 <td class="code no-comment"><pre>%s</pre></td>
54 <td class="code no-comment"><pre>%s</pre></td>
54 </tr>
55 </tr>
55 </table>''' % str_
56 </table>''' % str_
56
57
57
58
58 def wrapped_diff(filenode_old, filenode_new, cut_off_limit=None,
59 def wrapped_diff(filenode_old, filenode_new, cut_off_limit=None,
59 ignore_whitespace=True, line_context=3,
60 ignore_whitespace=True, line_context=3,
60 enable_comments=False):
61 enable_comments=False):
61 """
62 """
62 returns a wrapped diff into a table, checks for cut_off_limit and presents
63 returns a wrapped diff into a table, checks for cut_off_limit and presents
63 proper message
64 proper message
64 """
65 """
65
66
66 if filenode_old is None:
67 if filenode_old is None:
67 filenode_old = FileNode(filenode_new.path, '', EmptyChangeset())
68 filenode_old = FileNode(filenode_new.path, '', EmptyChangeset())
68
69
69 if filenode_old.is_binary or filenode_new.is_binary:
70 if filenode_old.is_binary or filenode_new.is_binary:
70 diff = wrap_to_table(_('binary file'))
71 diff = wrap_to_table(_('binary file'))
71 stats = (0, 0)
72 stats = (0, 0)
72 size = 0
73 size = 0
73
74
74 elif cut_off_limit != -1 and (cut_off_limit is None or
75 elif cut_off_limit != -1 and (cut_off_limit is None or
75 (filenode_old.size < cut_off_limit and filenode_new.size < cut_off_limit)):
76 (filenode_old.size < cut_off_limit and filenode_new.size < cut_off_limit)):
76
77
77 f_gitdiff = get_gitdiff(filenode_old, filenode_new,
78 f_gitdiff = get_gitdiff(filenode_old, filenode_new,
78 ignore_whitespace=ignore_whitespace,
79 ignore_whitespace=ignore_whitespace,
79 context=line_context)
80 context=line_context)
80 diff_processor = DiffProcessor(f_gitdiff, format='gitdiff')
81 diff_processor = DiffProcessor(f_gitdiff, format='gitdiff')
81
82
82 diff = diff_processor.as_html(enable_comments=enable_comments)
83 diff = diff_processor.as_html(enable_comments=enable_comments)
83 stats = diff_processor.stat()
84 stats = diff_processor.stat()
84 size = len(diff or '')
85 size = len(diff or '')
85 else:
86 else:
86 diff = wrap_to_table(_('Changeset was too big and was cut off, use '
87 diff = wrap_to_table(_('Changeset was too big and was cut off, use '
87 'diff menu to display this diff'))
88 'diff menu to display this diff'))
88 stats = (0, 0)
89 stats = (0, 0)
89 size = 0
90 size = 0
90 if not diff:
91 if not diff:
91 submodules = filter(lambda o: isinstance(o, SubModuleNode),
92 submodules = filter(lambda o: isinstance(o, SubModuleNode),
92 [filenode_new, filenode_old])
93 [filenode_new, filenode_old])
93 if submodules:
94 if submodules:
94 diff = wrap_to_table(escape('Submodule %r' % submodules[0]))
95 diff = wrap_to_table(escape('Submodule %r' % submodules[0]))
95 else:
96 else:
96 diff = wrap_to_table(_('No changes detected'))
97 diff = wrap_to_table(_('No changes detected'))
97
98
98 cs1 = filenode_old.changeset.raw_id
99 cs1 = filenode_old.changeset.raw_id
99 cs2 = filenode_new.changeset.raw_id
100 cs2 = filenode_new.changeset.raw_id
100
101
101 return size, cs1, cs2, diff, stats
102 return size, cs1, cs2, diff, stats
102
103
103
104
104 def get_gitdiff(filenode_old, filenode_new, ignore_whitespace=True, context=3):
105 def get_gitdiff(filenode_old, filenode_new, ignore_whitespace=True, context=3):
105 """
106 """
106 Returns git style diff between given ``filenode_old`` and ``filenode_new``.
107 Returns git style diff between given ``filenode_old`` and ``filenode_new``.
107
108
108 :param ignore_whitespace: ignore whitespaces in diff
109 :param ignore_whitespace: ignore whitespaces in diff
109 """
110 """
110 # make sure we pass in default context
111 # make sure we pass in default context
111 context = context or 3
112 context = context or 3
112 submodules = filter(lambda o: isinstance(o, SubModuleNode),
113 submodules = filter(lambda o: isinstance(o, SubModuleNode),
113 [filenode_new, filenode_old])
114 [filenode_new, filenode_old])
114 if submodules:
115 if submodules:
115 return ''
116 return ''
116
117
117 for filenode in (filenode_old, filenode_new):
118 for filenode in (filenode_old, filenode_new):
118 if not isinstance(filenode, FileNode):
119 if not isinstance(filenode, FileNode):
119 raise VCSError("Given object should be FileNode object, not %s"
120 raise VCSError("Given object should be FileNode object, not %s"
120 % filenode.__class__)
121 % filenode.__class__)
121
122
122 repo = filenode_new.changeset.repository
123 repo = filenode_new.changeset.repository
123 old_raw_id = getattr(filenode_old.changeset, 'raw_id', repo.EMPTY_CHANGESET)
124 old_raw_id = getattr(filenode_old.changeset, 'raw_id', repo.EMPTY_CHANGESET)
124 new_raw_id = getattr(filenode_new.changeset, 'raw_id', repo.EMPTY_CHANGESET)
125 new_raw_id = getattr(filenode_new.changeset, 'raw_id', repo.EMPTY_CHANGESET)
125
126
126 vcs_gitdiff = repo.get_diff(old_raw_id, new_raw_id, filenode_new.path,
127 vcs_gitdiff = repo.get_diff(old_raw_id, new_raw_id, filenode_new.path,
127 ignore_whitespace, context)
128 ignore_whitespace, context)
128 return vcs_gitdiff
129 return vcs_gitdiff
129
130
130
131
131 class DiffProcessor(object):
132 class DiffProcessor(object):
132 """
133 """
133 Give it a unified diff and it returns a list of the files that were
134 Give it a unified diff and it returns a list of the files that were
134 mentioned in the diff together with a dict of meta information that
135 mentioned in the diff together with a dict of meta information that
135 can be used to render it in a HTML template.
136 can be used to render it in a HTML template.
136 """
137 """
137 _chunk_re = re.compile(r'@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@(.*)')
138 _chunk_re = re.compile(r'@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@(.*)')
138 _newline_marker = '\\ No newline at end of file\n'
139 _newline_marker = '\\ No newline at end of file\n'
139
140
140 def __init__(self, diff, differ='diff', format='gitdiff'):
141 def __init__(self, diff, differ='diff', format='gitdiff'):
141 """
142 """
142 :param diff: a text in diff format or generator
143 :param diff: a text in diff format or generator
143 :param format: format of diff passed, `udiff` or `gitdiff`
144 :param format: format of diff passed, `udiff` or `gitdiff`
144 """
145 """
145 if isinstance(diff, basestring):
146 if isinstance(diff, basestring):
146 diff = [diff]
147 diff = [diff]
147
148
148 self.__udiff = diff
149 self.__udiff = diff
149 self.__format = format
150 self.__format = format
150 self.adds = 0
151 self.adds = 0
151 self.removes = 0
152 self.removes = 0
152
153
153 if isinstance(self.__udiff, basestring):
154 if isinstance(self.__udiff, basestring):
154 self.lines = iter(self.__udiff.splitlines(1))
155 self.lines = iter(self.__udiff.splitlines(1))
155
156
156 elif self.__format == 'gitdiff':
157 elif self.__format == 'gitdiff':
157 udiff_copy = self.copy_iterator()
158 udiff_copy = self.copy_iterator()
158 self.lines = imap(self.escaper, self._parse_gitdiff(udiff_copy))
159 self.lines = imap(self.escaper, self._parse_gitdiff(udiff_copy))
159 else:
160 else:
160 udiff_copy = self.copy_iterator()
161 udiff_copy = self.copy_iterator()
161 self.lines = imap(self.escaper, udiff_copy)
162 self.lines = imap(self.escaper, udiff_copy)
162
163
163 # Select a differ.
164 # Select a differ.
164 if differ == 'difflib':
165 if differ == 'difflib':
165 self.differ = self._highlight_line_difflib
166 self.differ = self._highlight_line_difflib
166 else:
167 else:
167 self.differ = self._highlight_line_udiff
168 self.differ = self._highlight_line_udiff
168
169
169 def escaper(self, string):
170 def escaper(self, string):
170 return markupsafe.escape(string)
171 return markupsafe.escape(string)
171
172
172 def copy_iterator(self):
173 def copy_iterator(self):
173 """
174 """
174 make a fresh copy of generator, we should not iterate thru
175 make a fresh copy of generator, we should not iterate thru
175 an original as it's needed for repeating operations on
176 an original as it's needed for repeating operations on
176 this instance of DiffProcessor
177 this instance of DiffProcessor
177 """
178 """
178 self.__udiff, iterator_copy = tee(self.__udiff)
179 self.__udiff, iterator_copy = tee(self.__udiff)
179 return iterator_copy
180 return iterator_copy
180
181
181 def _extract_rev(self, line1, line2):
182 def _extract_rev(self, line1, line2):
182 """
183 """
183 Extract the operation (A/M/D), filename and revision hint from a line.
184 Extract the operation (A/M/D), filename and revision hint from a line.
184 """
185 """
185
186
186 try:
187 try:
187 if line1.startswith('--- ') and line2.startswith('+++ '):
188 if line1.startswith('--- ') and line2.startswith('+++ '):
188 l1 = line1[4:].split(None, 1)
189 l1 = line1[4:].split(None, 1)
189 old_filename = (l1[0].replace('a/', '', 1)
190 old_filename = (l1[0].replace('a/', '', 1)
190 if len(l1) >= 1 else None)
191 if len(l1) >= 1 else None)
191 old_rev = l1[1] if len(l1) == 2 else 'old'
192 old_rev = l1[1] if len(l1) == 2 else 'old'
192
193
193 l2 = line2[4:].split(None, 1)
194 l2 = line2[4:].split(None, 1)
194 new_filename = (l2[0].replace('b/', '', 1)
195 new_filename = (l2[0].replace('b/', '', 1)
195 if len(l1) >= 1 else None)
196 if len(l1) >= 1 else None)
196 new_rev = l2[1] if len(l2) == 2 else 'new'
197 new_rev = l2[1] if len(l2) == 2 else 'new'
197
198
198 filename = (old_filename
199 filename = (old_filename
199 if old_filename != '/dev/null' else new_filename)
200 if old_filename != '/dev/null' else new_filename)
200
201
201 operation = 'D' if new_filename == '/dev/null' else None
202 operation = 'D' if new_filename == '/dev/null' else None
202 if not operation:
203 if not operation:
203 operation = 'M' if old_filename != '/dev/null' else 'A'
204 operation = 'M' if old_filename != '/dev/null' else 'A'
204
205
205 return operation, filename, new_rev, old_rev
206 return operation, filename, new_rev, old_rev
206 except (ValueError, IndexError):
207 except (ValueError, IndexError):
207 pass
208 pass
208
209
209 return None, None, None, None
210 return None, None, None, None
210
211
211 def _parse_gitdiff(self, diffiterator):
212 def _parse_gitdiff(self, diffiterator):
212 def line_decoder(l):
213 def line_decoder(l):
213 if l.startswith('+') and not l.startswith('+++'):
214 if l.startswith('+') and not l.startswith('+++'):
214 self.adds += 1
215 self.adds += 1
215 elif l.startswith('-') and not l.startswith('---'):
216 elif l.startswith('-') and not l.startswith('---'):
216 self.removes += 1
217 self.removes += 1
217 return l.decode('utf8', 'replace')
218 return safe_unicode(l)
218
219
219 output = list(diffiterator)
220 output = list(diffiterator)
220 size = len(output)
221 size = len(output)
221
222
222 if size == 2:
223 if size == 2:
223 l = []
224 l = []
224 l.extend([output[0]])
225 l.extend([output[0]])
225 l.extend(output[1].splitlines(1))
226 l.extend(output[1].splitlines(1))
226 return map(line_decoder, l)
227 return map(line_decoder, l)
227 elif size == 1:
228 elif size == 1:
228 return map(line_decoder, output[0].splitlines(1))
229 return map(line_decoder, output[0].splitlines(1))
229 elif size == 0:
230 elif size == 0:
230 return []
231 return []
231
232
232 raise Exception('wrong size of diff %s' % size)
233 raise Exception('wrong size of diff %s' % size)
233
234
234 def _highlight_line_difflib(self, line, next_):
235 def _highlight_line_difflib(self, line, next_):
235 """
236 """
236 Highlight inline changes in both lines.
237 Highlight inline changes in both lines.
237 """
238 """
238
239
239 if line['action'] == 'del':
240 if line['action'] == 'del':
240 old, new = line, next_
241 old, new = line, next_
241 else:
242 else:
242 old, new = next_, line
243 old, new = next_, line
243
244
244 oldwords = re.split(r'(\W)', old['line'])
245 oldwords = re.split(r'(\W)', old['line'])
245 newwords = re.split(r'(\W)', new['line'])
246 newwords = re.split(r'(\W)', new['line'])
246
247
247 sequence = difflib.SequenceMatcher(None, oldwords, newwords)
248 sequence = difflib.SequenceMatcher(None, oldwords, newwords)
248
249
249 oldfragments, newfragments = [], []
250 oldfragments, newfragments = [], []
250 for tag, i1, i2, j1, j2 in sequence.get_opcodes():
251 for tag, i1, i2, j1, j2 in sequence.get_opcodes():
251 oldfrag = ''.join(oldwords[i1:i2])
252 oldfrag = ''.join(oldwords[i1:i2])
252 newfrag = ''.join(newwords[j1:j2])
253 newfrag = ''.join(newwords[j1:j2])
253 if tag != 'equal':
254 if tag != 'equal':
254 if oldfrag:
255 if oldfrag:
255 oldfrag = '<del>%s</del>' % oldfrag
256 oldfrag = '<del>%s</del>' % oldfrag
256 if newfrag:
257 if newfrag:
257 newfrag = '<ins>%s</ins>' % newfrag
258 newfrag = '<ins>%s</ins>' % newfrag
258 oldfragments.append(oldfrag)
259 oldfragments.append(oldfrag)
259 newfragments.append(newfrag)
260 newfragments.append(newfrag)
260
261
261 old['line'] = "".join(oldfragments)
262 old['line'] = "".join(oldfragments)
262 new['line'] = "".join(newfragments)
263 new['line'] = "".join(newfragments)
263
264
264 def _highlight_line_udiff(self, line, next_):
265 def _highlight_line_udiff(self, line, next_):
265 """
266 """
266 Highlight inline changes in both lines.
267 Highlight inline changes in both lines.
267 """
268 """
268 start = 0
269 start = 0
269 limit = min(len(line['line']), len(next_['line']))
270 limit = min(len(line['line']), len(next_['line']))
270 while start < limit and line['line'][start] == next_['line'][start]:
271 while start < limit and line['line'][start] == next_['line'][start]:
271 start += 1
272 start += 1
272 end = -1
273 end = -1
273 limit -= start
274 limit -= start
274 while -end <= limit and line['line'][end] == next_['line'][end]:
275 while -end <= limit and line['line'][end] == next_['line'][end]:
275 end -= 1
276 end -= 1
276 end += 1
277 end += 1
277 if start or end:
278 if start or end:
278 def do(l):
279 def do(l):
279 last = end + len(l['line'])
280 last = end + len(l['line'])
280 if l['action'] == 'add':
281 if l['action'] == 'add':
281 tag = 'ins'
282 tag = 'ins'
282 else:
283 else:
283 tag = 'del'
284 tag = 'del'
284 l['line'] = '%s<%s>%s</%s>%s' % (
285 l['line'] = '%s<%s>%s</%s>%s' % (
285 l['line'][:start],
286 l['line'][:start],
286 tag,
287 tag,
287 l['line'][start:last],
288 l['line'][start:last],
288 tag,
289 tag,
289 l['line'][last:]
290 l['line'][last:]
290 )
291 )
291 do(line)
292 do(line)
292 do(next_)
293 do(next_)
293
294
294 def _parse_udiff(self, inline_diff=True):
295 def _parse_udiff(self, inline_diff=True):
295 """
296 """
296 Parse the diff an return data for the template.
297 Parse the diff an return data for the template.
297 """
298 """
298 lineiter = self.lines
299 lineiter = self.lines
299 files = []
300 files = []
300 try:
301 try:
301 line = lineiter.next()
302 line = lineiter.next()
302 while 1:
303 while 1:
303 # continue until we found the old file
304 # continue until we found the old file
304 if not line.startswith('--- '):
305 if not line.startswith('--- '):
305 line = lineiter.next()
306 line = lineiter.next()
306 continue
307 continue
307
308
308 chunks = []
309 chunks = []
309 stats = [0, 0]
310 stats = [0, 0]
310 operation, filename, old_rev, new_rev = \
311 operation, filename, old_rev, new_rev = \
311 self._extract_rev(line, lineiter.next())
312 self._extract_rev(line, lineiter.next())
312 files.append({
313 files.append({
313 'filename': filename,
314 'filename': filename,
314 'old_revision': old_rev,
315 'old_revision': old_rev,
315 'new_revision': new_rev,
316 'new_revision': new_rev,
316 'chunks': chunks,
317 'chunks': chunks,
317 'operation': operation,
318 'operation': operation,
318 'stats': stats,
319 'stats': stats,
319 })
320 })
320
321
321 line = lineiter.next()
322 line = lineiter.next()
322 while line:
323 while line:
323 match = self._chunk_re.match(line)
324 match = self._chunk_re.match(line)
324 if not match:
325 if not match:
325 break
326 break
326
327
327 lines = []
328 lines = []
328 chunks.append(lines)
329 chunks.append(lines)
329
330
330 old_line, old_end, new_line, new_end = \
331 old_line, old_end, new_line, new_end = \
331 [int(x or 1) for x in match.groups()[:-1]]
332 [int(x or 1) for x in match.groups()[:-1]]
332 old_line -= 1
333 old_line -= 1
333 new_line -= 1
334 new_line -= 1
334 gr = match.groups()
335 gr = match.groups()
335 context = len(gr) == 5
336 context = len(gr) == 5
336 old_end += old_line
337 old_end += old_line
337 new_end += new_line
338 new_end += new_line
338
339
339 if context:
340 if context:
340 # skip context only if it's first line
341 # skip context only if it's first line
341 if int(gr[0]) > 1:
342 if int(gr[0]) > 1:
342 lines.append({
343 lines.append({
343 'old_lineno': '...',
344 'old_lineno': '...',
344 'new_lineno': '...',
345 'new_lineno': '...',
345 'action': 'context',
346 'action': 'context',
346 'line': line,
347 'line': line,
347 })
348 })
348
349
349 line = lineiter.next()
350 line = lineiter.next()
350
351
351 while old_line < old_end or new_line < new_end:
352 while old_line < old_end or new_line < new_end:
352 if line:
353 if line:
353 command = line[0]
354 command = line[0]
354 if command in ['+', '-', ' ']:
355 if command in ['+', '-', ' ']:
355 #only modify the line if it's actually a diff
356 #only modify the line if it's actually a diff
356 # thing
357 # thing
357 line = line[1:]
358 line = line[1:]
358 else:
359 else:
359 command = ' '
360 command = ' '
360
361
361 affects_old = affects_new = False
362 affects_old = affects_new = False
362
363
363 # ignore those if we don't expect them
364 # ignore those if we don't expect them
364 if command in '#@':
365 if command in '#@':
365 continue
366 continue
366 elif command == '+':
367 elif command == '+':
367 affects_new = True
368 affects_new = True
368 action = 'add'
369 action = 'add'
369 stats[0] += 1
370 stats[0] += 1
370 elif command == '-':
371 elif command == '-':
371 affects_old = True
372 affects_old = True
372 action = 'del'
373 action = 'del'
373 stats[1] += 1
374 stats[1] += 1
374 else:
375 else:
375 affects_old = affects_new = True
376 affects_old = affects_new = True
376 action = 'unmod'
377 action = 'unmod'
377
378
378 if line != self._newline_marker:
379 if line != self._newline_marker:
379 old_line += affects_old
380 old_line += affects_old
380 new_line += affects_new
381 new_line += affects_new
381 lines.append({
382 lines.append({
382 'old_lineno': affects_old and old_line or '',
383 'old_lineno': affects_old and old_line or '',
383 'new_lineno': affects_new and new_line or '',
384 'new_lineno': affects_new and new_line or '',
384 'action': action,
385 'action': action,
385 'line': line
386 'line': line
386 })
387 })
387
388
388 line = lineiter.next()
389 line = lineiter.next()
389 if line == self._newline_marker:
390 if line == self._newline_marker:
390 # we need to append to lines, since this is not
391 # we need to append to lines, since this is not
391 # counted in the line specs of diff
392 # counted in the line specs of diff
392 lines.append({
393 lines.append({
393 'old_lineno': '...',
394 'old_lineno': '...',
394 'new_lineno': '...',
395 'new_lineno': '...',
395 'action': 'context',
396 'action': 'context',
396 'line': line
397 'line': line
397 })
398 })
398
399
399 except StopIteration:
400 except StopIteration:
400 pass
401 pass
401
402
402 sorter = lambda info: {'A': 0, 'M': 1, 'D': 2}.get(info['operation'])
403 sorter = lambda info: {'A': 0, 'M': 1, 'D': 2}.get(info['operation'])
403 if inline_diff is False:
404 if inline_diff is False:
404 return sorted(files, key=sorter)
405 return sorted(files, key=sorter)
405
406
406 # highlight inline changes
407 # highlight inline changes
407 for diff_data in files:
408 for diff_data in files:
408 for chunk in diff_data['chunks']:
409 for chunk in diff_data['chunks']:
409 lineiter = iter(chunk)
410 lineiter = iter(chunk)
410 try:
411 try:
411 while 1:
412 while 1:
412 line = lineiter.next()
413 line = lineiter.next()
413 if line['action'] not in ['unmod', 'context']:
414 if line['action'] not in ['unmod', 'context']:
414 nextline = lineiter.next()
415 nextline = lineiter.next()
415 if nextline['action'] in ['unmod', 'context'] or \
416 if nextline['action'] in ['unmod', 'context'] or \
416 nextline['action'] == line['action']:
417 nextline['action'] == line['action']:
417 continue
418 continue
418 self.differ(line, nextline)
419 self.differ(line, nextline)
419 except StopIteration:
420 except StopIteration:
420 pass
421 pass
421
422
422 return sorted(files, key=sorter)
423 return sorted(files, key=sorter)
423
424
424 def prepare(self, inline_diff=True):
425 def prepare(self, inline_diff=True):
425 """
426 """
426 Prepare the passed udiff for HTML rendering. It'l return a list
427 Prepare the passed udiff for HTML rendering. It'l return a list
427 of dicts
428 of dicts
428 """
429 """
429 return self._parse_udiff(inline_diff=inline_diff)
430 return self._parse_udiff(inline_diff=inline_diff)
430
431
431 def _safe_id(self, idstring):
432 def _safe_id(self, idstring):
432 """Make a string safe for including in an id attribute.
433 """Make a string safe for including in an id attribute.
433
434
434 The HTML spec says that id attributes 'must begin with
435 The HTML spec says that id attributes 'must begin with
435 a letter ([A-Za-z]) and may be followed by any number
436 a letter ([A-Za-z]) and may be followed by any number
436 of letters, digits ([0-9]), hyphens ("-"), underscores
437 of letters, digits ([0-9]), hyphens ("-"), underscores
437 ("_"), colons (":"), and periods (".")'. These regexps
438 ("_"), colons (":"), and periods (".")'. These regexps
438 are slightly over-zealous, in that they remove colons
439 are slightly over-zealous, in that they remove colons
439 and periods unnecessarily.
440 and periods unnecessarily.
440
441
441 Whitespace is transformed into underscores, and then
442 Whitespace is transformed into underscores, and then
442 anything which is not a hyphen or a character that
443 anything which is not a hyphen or a character that
443 matches \w (alphanumerics and underscore) is removed.
444 matches \w (alphanumerics and underscore) is removed.
444
445
445 """
446 """
446 # Transform all whitespace to underscore
447 # Transform all whitespace to underscore
447 idstring = re.sub(r'\s', "_", '%s' % idstring)
448 idstring = re.sub(r'\s', "_", '%s' % idstring)
448 # Remove everything that is not a hyphen or a member of \w
449 # Remove everything that is not a hyphen or a member of \w
449 idstring = re.sub(r'(?!-)\W', "", idstring).lower()
450 idstring = re.sub(r'(?!-)\W', "", idstring).lower()
450 return idstring
451 return idstring
451
452
452 def raw_diff(self):
453 def raw_diff(self):
453 """
454 """
454 Returns raw string as udiff
455 Returns raw string as udiff
455 """
456 """
456 udiff_copy = self.copy_iterator()
457 udiff_copy = self.copy_iterator()
457 if self.__format == 'gitdiff':
458 if self.__format == 'gitdiff':
458 udiff_copy = self._parse_gitdiff(udiff_copy)
459 udiff_copy = self._parse_gitdiff(udiff_copy)
459 return u''.join(udiff_copy)
460 return u''.join(udiff_copy)
460
461
461 def as_html(self, table_class='code-difftable', line_class='line',
462 def as_html(self, table_class='code-difftable', line_class='line',
462 new_lineno_class='lineno old', old_lineno_class='lineno new',
463 new_lineno_class='lineno old', old_lineno_class='lineno new',
463 code_class='code', enable_comments=False, diff_lines=None):
464 code_class='code', enable_comments=False, diff_lines=None):
464 """
465 """
465 Return given diff as html table with customized css classes
466 Return given diff as html table with customized css classes
466 """
467 """
467 def _link_to_if(condition, label, url):
468 def _link_to_if(condition, label, url):
468 """
469 """
469 Generates a link if condition is meet or just the label if not.
470 Generates a link if condition is meet or just the label if not.
470 """
471 """
471
472
472 if condition:
473 if condition:
473 return '''<a href="%(url)s">%(label)s</a>''' % {
474 return '''<a href="%(url)s">%(label)s</a>''' % {
474 'url': url,
475 'url': url,
475 'label': label
476 'label': label
476 }
477 }
477 else:
478 else:
478 return label
479 return label
479 if diff_lines is None:
480 if diff_lines is None:
480 diff_lines = self.prepare()
481 diff_lines = self.prepare()
481 _html_empty = True
482 _html_empty = True
482 _html = []
483 _html = []
483 _html.append('''<table class="%(table_class)s">\n''' % {
484 _html.append('''<table class="%(table_class)s">\n''' % {
484 'table_class': table_class
485 'table_class': table_class
485 })
486 })
486 for diff in diff_lines:
487 for diff in diff_lines:
487 for line in diff['chunks']:
488 for line in diff['chunks']:
488 _html_empty = False
489 _html_empty = False
489 for change in line:
490 for change in line:
490 _html.append('''<tr class="%(lc)s %(action)s">\n''' % {
491 _html.append('''<tr class="%(lc)s %(action)s">\n''' % {
491 'lc': line_class,
492 'lc': line_class,
492 'action': change['action']
493 'action': change['action']
493 })
494 })
494 anchor_old_id = ''
495 anchor_old_id = ''
495 anchor_new_id = ''
496 anchor_new_id = ''
496 anchor_old = "%(filename)s_o%(oldline_no)s" % {
497 anchor_old = "%(filename)s_o%(oldline_no)s" % {
497 'filename': self._safe_id(diff['filename']),
498 'filename': self._safe_id(diff['filename']),
498 'oldline_no': change['old_lineno']
499 'oldline_no': change['old_lineno']
499 }
500 }
500 anchor_new = "%(filename)s_n%(oldline_no)s" % {
501 anchor_new = "%(filename)s_n%(oldline_no)s" % {
501 'filename': self._safe_id(diff['filename']),
502 'filename': self._safe_id(diff['filename']),
502 'oldline_no': change['new_lineno']
503 'oldline_no': change['new_lineno']
503 }
504 }
504 cond_old = (change['old_lineno'] != '...' and
505 cond_old = (change['old_lineno'] != '...' and
505 change['old_lineno'])
506 change['old_lineno'])
506 cond_new = (change['new_lineno'] != '...' and
507 cond_new = (change['new_lineno'] != '...' and
507 change['new_lineno'])
508 change['new_lineno'])
508 if cond_old:
509 if cond_old:
509 anchor_old_id = 'id="%s"' % anchor_old
510 anchor_old_id = 'id="%s"' % anchor_old
510 if cond_new:
511 if cond_new:
511 anchor_new_id = 'id="%s"' % anchor_new
512 anchor_new_id = 'id="%s"' % anchor_new
512 ###########################################################
513 ###########################################################
513 # OLD LINE NUMBER
514 # OLD LINE NUMBER
514 ###########################################################
515 ###########################################################
515 _html.append('''\t<td %(a_id)s class="%(olc)s">''' % {
516 _html.append('''\t<td %(a_id)s class="%(olc)s">''' % {
516 'a_id': anchor_old_id,
517 'a_id': anchor_old_id,
517 'olc': old_lineno_class
518 'olc': old_lineno_class
518 })
519 })
519
520
520 _html.append('''%(link)s''' % {
521 _html.append('''%(link)s''' % {
521 'link': _link_to_if(True, change['old_lineno'],
522 'link': _link_to_if(True, change['old_lineno'],
522 '#%s' % anchor_old)
523 '#%s' % anchor_old)
523 })
524 })
524 _html.append('''</td>\n''')
525 _html.append('''</td>\n''')
525 ###########################################################
526 ###########################################################
526 # NEW LINE NUMBER
527 # NEW LINE NUMBER
527 ###########################################################
528 ###########################################################
528
529
529 _html.append('''\t<td %(a_id)s class="%(nlc)s">''' % {
530 _html.append('''\t<td %(a_id)s class="%(nlc)s">''' % {
530 'a_id': anchor_new_id,
531 'a_id': anchor_new_id,
531 'nlc': new_lineno_class
532 'nlc': new_lineno_class
532 })
533 })
533
534
534 _html.append('''%(link)s''' % {
535 _html.append('''%(link)s''' % {
535 'link': _link_to_if(True, change['new_lineno'],
536 'link': _link_to_if(True, change['new_lineno'],
536 '#%s' % anchor_new)
537 '#%s' % anchor_new)
537 })
538 })
538 _html.append('''</td>\n''')
539 _html.append('''</td>\n''')
539 ###########################################################
540 ###########################################################
540 # CODE
541 # CODE
541 ###########################################################
542 ###########################################################
542 comments = '' if enable_comments else 'no-comment'
543 comments = '' if enable_comments else 'no-comment'
543 _html.append('''\t<td class="%(cc)s %(inc)s">''' % {
544 _html.append('''\t<td class="%(cc)s %(inc)s">''' % {
544 'cc': code_class,
545 'cc': code_class,
545 'inc': comments
546 'inc': comments
546 })
547 })
547 _html.append('''\n\t\t<pre>%(code)s</pre>\n''' % {
548 _html.append('''\n\t\t<pre>%(code)s</pre>\n''' % {
548 'code': change['line']
549 'code': change['line']
549 })
550 })
550 _html.append('''\t</td>''')
551 _html.append('''\t</td>''')
551 _html.append('''\n</tr>\n''')
552 _html.append('''\n</tr>\n''')
552 _html.append('''</table>''')
553 _html.append('''</table>''')
553 if _html_empty:
554 if _html_empty:
554 return None
555 return None
555 return ''.join(_html)
556 return ''.join(_html)
556
557
557 def stat(self):
558 def stat(self):
558 """
559 """
559 Returns tuple of added, and removed lines for this instance
560 Returns tuple of added, and removed lines for this instance
560 """
561 """
561 return self.adds, self.removes
562 return self.adds, self.removes
562
563
563
564
564 class InMemoryBundleRepo(bundlerepository):
565 class InMemoryBundleRepo(bundlerepository):
565 def __init__(self, ui, path, bundlestream):
566 def __init__(self, ui, path, bundlestream):
566 self._tempparent = None
567 self._tempparent = None
567 localrepo.localrepository.__init__(self, ui, path)
568 localrepo.localrepository.__init__(self, ui, path)
568 self.ui.setconfig('phases', 'publish', False)
569 self.ui.setconfig('phases', 'publish', False)
569
570
570 self.bundle = bundlestream
571 self.bundle = bundlestream
571
572
572 # dict with the mapping 'filename' -> position in the bundle
573 # dict with the mapping 'filename' -> position in the bundle
573 self.bundlefilespos = {}
574 self.bundlefilespos = {}
574
575
575
576
576 def differ(org_repo, org_ref, other_repo, other_ref, discovery_data=None):
577 def differ(org_repo, org_ref, other_repo, other_ref, discovery_data=None):
577 """
578 """
578 General differ between branches, bookmarks or separate but releated
579 General differ between branches, bookmarks or separate but releated
579 repositories
580 repositories
580
581
581 :param org_repo:
582 :param org_repo:
582 :type org_repo:
583 :type org_repo:
583 :param org_ref:
584 :param org_ref:
584 :type org_ref:
585 :type org_ref:
585 :param other_repo:
586 :param other_repo:
586 :type other_repo:
587 :type other_repo:
587 :param other_ref:
588 :param other_ref:
588 :type other_ref:
589 :type other_ref:
589 """
590 """
590
591
591 bundlerepo = None
592 bundlerepo = None
592 ignore_whitespace = False
593 ignore_whitespace = False
593 context = 3
594 context = 3
594 org_repo = org_repo.scm_instance._repo
595 org_repo = org_repo.scm_instance._repo
595 other_repo = other_repo.scm_instance._repo
596 other_repo = other_repo.scm_instance._repo
596 opts = diffopts(git=True, ignorews=ignore_whitespace, context=context)
597 opts = diffopts(git=True, ignorews=ignore_whitespace, context=context)
597 org_ref = org_ref[1]
598 org_ref = org_ref[1]
598 other_ref = other_ref[1]
599 other_ref = other_ref[1]
599
600
600 if org_repo != other_repo:
601 if org_repo != other_repo:
601
602
602 common, incoming, rheads = discovery_data
603 common, incoming, rheads = discovery_data
603 other_repo_peer = localrepo.locallegacypeer(other_repo.local())
604 other_repo_peer = localrepo.locallegacypeer(other_repo.local())
604 # create a bundle (uncompressed if other repo is not local)
605 # create a bundle (uncompressed if other repo is not local)
605 if other_repo_peer.capable('getbundle') and incoming:
606 if other_repo_peer.capable('getbundle') and incoming:
606 # disable repo hooks here since it's just bundle !
607 # disable repo hooks here since it's just bundle !
607 # patch and reset hooks section of UI config to not run any
608 # patch and reset hooks section of UI config to not run any
608 # hooks on fetching archives with subrepos
609 # hooks on fetching archives with subrepos
609 for k, _ in other_repo.ui.configitems('hooks'):
610 for k, _ in other_repo.ui.configitems('hooks'):
610 other_repo.ui.setconfig('hooks', k, None)
611 other_repo.ui.setconfig('hooks', k, None)
611
612
612 unbundle = other_repo.getbundle('incoming', common=common,
613 unbundle = other_repo.getbundle('incoming', common=common,
613 heads=None)
614 heads=None)
614
615
615 buf = BytesIO()
616 buf = BytesIO()
616 while True:
617 while True:
617 chunk = unbundle._stream.read(1024 * 4)
618 chunk = unbundle._stream.read(1024 * 4)
618 if not chunk:
619 if not chunk:
619 break
620 break
620 buf.write(chunk)
621 buf.write(chunk)
621
622
622 buf.seek(0)
623 buf.seek(0)
623 # replace chunked _stream with data that can do tell() and seek()
624 # replace chunked _stream with data that can do tell() and seek()
624 unbundle._stream = buf
625 unbundle._stream = buf
625
626
626 ui = make_ui('db')
627 ui = make_ui('db')
627 bundlerepo = InMemoryBundleRepo(ui, path=org_repo.root,
628 bundlerepo = InMemoryBundleRepo(ui, path=org_repo.root,
628 bundlestream=unbundle)
629 bundlestream=unbundle)
629
630
630 return ''.join(patch.diff(bundlerepo or org_repo,
631 return ''.join(patch.diff(bundlerepo or org_repo,
631 node1=org_repo[org_ref].node(),
632 node1=org_repo[org_ref].node(),
632 node2=other_repo[other_ref].node(),
633 node2=other_repo[other_ref].node(),
633 opts=opts))
634 opts=opts))
634 else:
635 else:
635 return ''.join(patch.diff(org_repo, node1=org_ref, node2=other_ref,
636 return ''.join(patch.diff(org_repo, node1=org_ref, node2=other_ref,
636 opts=opts))
637 opts=opts))
@@ -1,1072 +1,1077 b''
1 """Helper functions
1 """Helper functions
2
2
3 Consists of functions to typically be used within templates, but also
3 Consists of functions to typically be used within templates, but also
4 available to Controllers. This module is available to both as 'h'.
4 available to Controllers. This module is available to both as 'h'.
5 """
5 """
6 import random
6 import random
7 import hashlib
7 import hashlib
8 import StringIO
8 import StringIO
9 import urllib
9 import urllib
10 import math
10 import math
11 import logging
11 import logging
12 import re
12 import re
13 import urlparse
13
14
14 from datetime import datetime
15 from datetime import datetime
15 from pygments.formatters.html import HtmlFormatter
16 from pygments.formatters.html import HtmlFormatter
16 from pygments import highlight as code_highlight
17 from pygments import highlight as code_highlight
17 from pylons import url, request, config
18 from pylons import url, request, config
18 from pylons.i18n.translation import _, ungettext
19 from pylons.i18n.translation import _, ungettext
19 from hashlib import md5
20 from hashlib import md5
20
21
21 from webhelpers.html import literal, HTML, escape
22 from webhelpers.html import literal, HTML, escape
22 from webhelpers.html.tools import *
23 from webhelpers.html.tools import *
23 from webhelpers.html.builder import make_tag
24 from webhelpers.html.builder import make_tag
24 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
25 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
25 end_form, file, form, hidden, image, javascript_link, link_to, \
26 end_form, file, form, hidden, image, javascript_link, link_to, \
26 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
27 link_to_if, link_to_unless, ol, required_legend, select, stylesheet_link, \
27 submit, text, password, textarea, title, ul, xml_declaration, radio
28 submit, text, password, textarea, title, ul, xml_declaration, radio
28 from webhelpers.html.tools import auto_link, button_to, highlight, \
29 from webhelpers.html.tools import auto_link, button_to, highlight, \
29 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
30 js_obfuscate, mail_to, strip_links, strip_tags, tag_re
30 from webhelpers.number import format_byte_size, format_bit_size
31 from webhelpers.number import format_byte_size, format_bit_size
31 from webhelpers.pylonslib import Flash as _Flash
32 from webhelpers.pylonslib import Flash as _Flash
32 from webhelpers.pylonslib.secure_form import secure_form
33 from webhelpers.pylonslib.secure_form import secure_form
33 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
34 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
34 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
35 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
35 replace_whitespace, urlify, truncate, wrap_paragraphs
36 replace_whitespace, urlify, truncate, wrap_paragraphs
36 from webhelpers.date import time_ago_in_words
37 from webhelpers.date import time_ago_in_words
37 from webhelpers.paginate import Page
38 from webhelpers.paginate import Page
38 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
39 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
39 convert_boolean_attrs, NotGiven, _make_safe_id_component
40 convert_boolean_attrs, NotGiven, _make_safe_id_component
40
41
41 from rhodecode.lib.annotate import annotate_highlight
42 from rhodecode.lib.annotate import annotate_highlight
42 from rhodecode.lib.utils import repo_name_slug
43 from rhodecode.lib.utils import repo_name_slug
43 from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
44 from rhodecode.lib.utils2 import str2bool, safe_unicode, safe_str, \
44 get_changeset_safe, datetime_to_time, time_to_datetime
45 get_changeset_safe, datetime_to_time, time_to_datetime
45 from rhodecode.lib.markup_renderer import MarkupRenderer
46 from rhodecode.lib.markup_renderer import MarkupRenderer
46 from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError
47 from rhodecode.lib.vcs.exceptions import ChangesetDoesNotExistError
47 from rhodecode.lib.vcs.backends.base import BaseChangeset
48 from rhodecode.lib.vcs.backends.base import BaseChangeset
48 from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
49 from rhodecode.config.conf import DATE_FORMAT, DATETIME_FORMAT
49 from rhodecode.model.changeset_status import ChangesetStatusModel
50 from rhodecode.model.changeset_status import ChangesetStatusModel
50 from rhodecode.model.db import URL_SEP, Permission
51 from rhodecode.model.db import URL_SEP, Permission
51
52
52 log = logging.getLogger(__name__)
53 log = logging.getLogger(__name__)
53
54
54
55
55 html_escape_table = {
56 html_escape_table = {
56 "&": "&amp;",
57 "&": "&amp;",
57 '"': "&quot;",
58 '"': "&quot;",
58 "'": "&apos;",
59 "'": "&apos;",
59 ">": "&gt;",
60 ">": "&gt;",
60 "<": "&lt;",
61 "<": "&lt;",
61 }
62 }
62
63
63
64
64 def html_escape(text):
65 def html_escape(text):
65 """Produce entities within text."""
66 """Produce entities within text."""
66 return "".join(html_escape_table.get(c,c) for c in text)
67 return "".join(html_escape_table.get(c,c) for c in text)
67
68
68
69
69 def shorter(text, size=20):
70 def shorter(text, size=20):
70 postfix = '...'
71 postfix = '...'
71 if len(text) > size:
72 if len(text) > size:
72 return text[:size - len(postfix)] + postfix
73 return text[:size - len(postfix)] + postfix
73 return text
74 return text
74
75
75
76
76 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
77 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
77 """
78 """
78 Reset button
79 Reset button
79 """
80 """
80 _set_input_attrs(attrs, type, name, value)
81 _set_input_attrs(attrs, type, name, value)
81 _set_id_attr(attrs, id, name)
82 _set_id_attr(attrs, id, name)
82 convert_boolean_attrs(attrs, ["disabled"])
83 convert_boolean_attrs(attrs, ["disabled"])
83 return HTML.input(**attrs)
84 return HTML.input(**attrs)
84
85
85 reset = _reset
86 reset = _reset
86 safeid = _make_safe_id_component
87 safeid = _make_safe_id_component
87
88
88
89
89 def FID(raw_id, path):
90 def FID(raw_id, path):
90 """
91 """
91 Creates a uniqe ID for filenode based on it's hash of path and revision
92 Creates a uniqe ID for filenode based on it's hash of path and revision
92 it's safe to use in urls
93 it's safe to use in urls
93
94
94 :param raw_id:
95 :param raw_id:
95 :param path:
96 :param path:
96 """
97 """
97
98
98 return 'C-%s-%s' % (short_id(raw_id), md5(safe_str(path)).hexdigest()[:12])
99 return 'C-%s-%s' % (short_id(raw_id), md5(safe_str(path)).hexdigest()[:12])
99
100
100
101
101 def get_token():
102 def get_token():
102 """Return the current authentication token, creating one if one doesn't
103 """Return the current authentication token, creating one if one doesn't
103 already exist.
104 already exist.
104 """
105 """
105 token_key = "_authentication_token"
106 token_key = "_authentication_token"
106 from pylons import session
107 from pylons import session
107 if not token_key in session:
108 if not token_key in session:
108 try:
109 try:
109 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
110 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
110 except AttributeError: # Python < 2.4
111 except AttributeError: # Python < 2.4
111 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
112 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
112 session[token_key] = token
113 session[token_key] = token
113 if hasattr(session, 'save'):
114 if hasattr(session, 'save'):
114 session.save()
115 session.save()
115 return session[token_key]
116 return session[token_key]
116
117
117
118
118 class _GetError(object):
119 class _GetError(object):
119 """Get error from form_errors, and represent it as span wrapped error
120 """Get error from form_errors, and represent it as span wrapped error
120 message
121 message
121
122
122 :param field_name: field to fetch errors for
123 :param field_name: field to fetch errors for
123 :param form_errors: form errors dict
124 :param form_errors: form errors dict
124 """
125 """
125
126
126 def __call__(self, field_name, form_errors):
127 def __call__(self, field_name, form_errors):
127 tmpl = """<span class="error_msg">%s</span>"""
128 tmpl = """<span class="error_msg">%s</span>"""
128 if form_errors and field_name in form_errors:
129 if form_errors and field_name in form_errors:
129 return literal(tmpl % form_errors.get(field_name))
130 return literal(tmpl % form_errors.get(field_name))
130
131
131 get_error = _GetError()
132 get_error = _GetError()
132
133
133
134
134 class _ToolTip(object):
135 class _ToolTip(object):
135
136
136 def __call__(self, tooltip_title, trim_at=50):
137 def __call__(self, tooltip_title, trim_at=50):
137 """
138 """
138 Special function just to wrap our text into nice formatted
139 Special function just to wrap our text into nice formatted
139 autowrapped text
140 autowrapped text
140
141
141 :param tooltip_title:
142 :param tooltip_title:
142 """
143 """
143 tooltip_title = escape(tooltip_title)
144 tooltip_title = escape(tooltip_title)
144 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
145 tooltip_title = tooltip_title.replace('<', '&lt;').replace('>', '&gt;')
145 return tooltip_title
146 return tooltip_title
146 tooltip = _ToolTip()
147 tooltip = _ToolTip()
147
148
148
149
149 class _FilesBreadCrumbs(object):
150 class _FilesBreadCrumbs(object):
150
151
151 def __call__(self, repo_name, rev, paths):
152 def __call__(self, repo_name, rev, paths):
152 if isinstance(paths, str):
153 if isinstance(paths, str):
153 paths = safe_unicode(paths)
154 paths = safe_unicode(paths)
154 url_l = [link_to(repo_name, url('files_home',
155 url_l = [link_to(repo_name, url('files_home',
155 repo_name=repo_name,
156 repo_name=repo_name,
156 revision=rev, f_path=''),
157 revision=rev, f_path=''),
157 class_='ypjax-link')]
158 class_='ypjax-link')]
158 paths_l = paths.split('/')
159 paths_l = paths.split('/')
159 for cnt, p in enumerate(paths_l):
160 for cnt, p in enumerate(paths_l):
160 if p != '':
161 if p != '':
161 url_l.append(link_to(p,
162 url_l.append(link_to(p,
162 url('files_home',
163 url('files_home',
163 repo_name=repo_name,
164 repo_name=repo_name,
164 revision=rev,
165 revision=rev,
165 f_path='/'.join(paths_l[:cnt + 1])
166 f_path='/'.join(paths_l[:cnt + 1])
166 ),
167 ),
167 class_='ypjax-link'
168 class_='ypjax-link'
168 )
169 )
169 )
170 )
170
171
171 return literal('/'.join(url_l))
172 return literal('/'.join(url_l))
172
173
173 files_breadcrumbs = _FilesBreadCrumbs()
174 files_breadcrumbs = _FilesBreadCrumbs()
174
175
175
176
176 class CodeHtmlFormatter(HtmlFormatter):
177 class CodeHtmlFormatter(HtmlFormatter):
177 """
178 """
178 My code Html Formatter for source codes
179 My code Html Formatter for source codes
179 """
180 """
180
181
181 def wrap(self, source, outfile):
182 def wrap(self, source, outfile):
182 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
183 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
183
184
184 def _wrap_code(self, source):
185 def _wrap_code(self, source):
185 for cnt, it in enumerate(source):
186 for cnt, it in enumerate(source):
186 i, t = it
187 i, t = it
187 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
188 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
188 yield i, t
189 yield i, t
189
190
190 def _wrap_tablelinenos(self, inner):
191 def _wrap_tablelinenos(self, inner):
191 dummyoutfile = StringIO.StringIO()
192 dummyoutfile = StringIO.StringIO()
192 lncount = 0
193 lncount = 0
193 for t, line in inner:
194 for t, line in inner:
194 if t:
195 if t:
195 lncount += 1
196 lncount += 1
196 dummyoutfile.write(line)
197 dummyoutfile.write(line)
197
198
198 fl = self.linenostart
199 fl = self.linenostart
199 mw = len(str(lncount + fl - 1))
200 mw = len(str(lncount + fl - 1))
200 sp = self.linenospecial
201 sp = self.linenospecial
201 st = self.linenostep
202 st = self.linenostep
202 la = self.lineanchors
203 la = self.lineanchors
203 aln = self.anchorlinenos
204 aln = self.anchorlinenos
204 nocls = self.noclasses
205 nocls = self.noclasses
205 if sp:
206 if sp:
206 lines = []
207 lines = []
207
208
208 for i in range(fl, fl + lncount):
209 for i in range(fl, fl + lncount):
209 if i % st == 0:
210 if i % st == 0:
210 if i % sp == 0:
211 if i % sp == 0:
211 if aln:
212 if aln:
212 lines.append('<a href="#%s%d" class="special">%*d</a>' %
213 lines.append('<a href="#%s%d" class="special">%*d</a>' %
213 (la, i, mw, i))
214 (la, i, mw, i))
214 else:
215 else:
215 lines.append('<span class="special">%*d</span>' % (mw, i))
216 lines.append('<span class="special">%*d</span>' % (mw, i))
216 else:
217 else:
217 if aln:
218 if aln:
218 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
219 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
219 else:
220 else:
220 lines.append('%*d' % (mw, i))
221 lines.append('%*d' % (mw, i))
221 else:
222 else:
222 lines.append('')
223 lines.append('')
223 ls = '\n'.join(lines)
224 ls = '\n'.join(lines)
224 else:
225 else:
225 lines = []
226 lines = []
226 for i in range(fl, fl + lncount):
227 for i in range(fl, fl + lncount):
227 if i % st == 0:
228 if i % st == 0:
228 if aln:
229 if aln:
229 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
230 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
230 else:
231 else:
231 lines.append('%*d' % (mw, i))
232 lines.append('%*d' % (mw, i))
232 else:
233 else:
233 lines.append('')
234 lines.append('')
234 ls = '\n'.join(lines)
235 ls = '\n'.join(lines)
235
236
236 # in case you wonder about the seemingly redundant <div> here: since the
237 # in case you wonder about the seemingly redundant <div> here: since the
237 # content in the other cell also is wrapped in a div, some browsers in
238 # content in the other cell also is wrapped in a div, some browsers in
238 # some configurations seem to mess up the formatting...
239 # some configurations seem to mess up the formatting...
239 if nocls:
240 if nocls:
240 yield 0, ('<table class="%stable">' % self.cssclass +
241 yield 0, ('<table class="%stable">' % self.cssclass +
241 '<tr><td><div class="linenodiv" '
242 '<tr><td><div class="linenodiv" '
242 'style="background-color: #f0f0f0; padding-right: 10px">'
243 'style="background-color: #f0f0f0; padding-right: 10px">'
243 '<pre style="line-height: 125%">' +
244 '<pre style="line-height: 125%">' +
244 ls + '</pre></div></td><td id="hlcode" class="code">')
245 ls + '</pre></div></td><td id="hlcode" class="code">')
245 else:
246 else:
246 yield 0, ('<table class="%stable">' % self.cssclass +
247 yield 0, ('<table class="%stable">' % self.cssclass +
247 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
248 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
248 ls + '</pre></div></td><td id="hlcode" class="code">')
249 ls + '</pre></div></td><td id="hlcode" class="code">')
249 yield 0, dummyoutfile.getvalue()
250 yield 0, dummyoutfile.getvalue()
250 yield 0, '</td></tr></table>'
251 yield 0, '</td></tr></table>'
251
252
252
253
253 def pygmentize(filenode, **kwargs):
254 def pygmentize(filenode, **kwargs):
254 """pygmentize function using pygments
255 """pygmentize function using pygments
255
256
256 :param filenode:
257 :param filenode:
257 """
258 """
258
259
259 return literal(code_highlight(filenode.content,
260 return literal(code_highlight(filenode.content,
260 filenode.lexer, CodeHtmlFormatter(**kwargs)))
261 filenode.lexer, CodeHtmlFormatter(**kwargs)))
261
262
262
263
263 def pygmentize_annotation(repo_name, filenode, **kwargs):
264 def pygmentize_annotation(repo_name, filenode, **kwargs):
264 """
265 """
265 pygmentize function for annotation
266 pygmentize function for annotation
266
267
267 :param filenode:
268 :param filenode:
268 """
269 """
269
270
270 color_dict = {}
271 color_dict = {}
271
272
272 def gen_color(n=10000):
273 def gen_color(n=10000):
273 """generator for getting n of evenly distributed colors using
274 """generator for getting n of evenly distributed colors using
274 hsv color and golden ratio. It always return same order of colors
275 hsv color and golden ratio. It always return same order of colors
275
276
276 :returns: RGB tuple
277 :returns: RGB tuple
277 """
278 """
278
279
279 def hsv_to_rgb(h, s, v):
280 def hsv_to_rgb(h, s, v):
280 if s == 0.0:
281 if s == 0.0:
281 return v, v, v
282 return v, v, v
282 i = int(h * 6.0) # XXX assume int() truncates!
283 i = int(h * 6.0) # XXX assume int() truncates!
283 f = (h * 6.0) - i
284 f = (h * 6.0) - i
284 p = v * (1.0 - s)
285 p = v * (1.0 - s)
285 q = v * (1.0 - s * f)
286 q = v * (1.0 - s * f)
286 t = v * (1.0 - s * (1.0 - f))
287 t = v * (1.0 - s * (1.0 - f))
287 i = i % 6
288 i = i % 6
288 if i == 0:
289 if i == 0:
289 return v, t, p
290 return v, t, p
290 if i == 1:
291 if i == 1:
291 return q, v, p
292 return q, v, p
292 if i == 2:
293 if i == 2:
293 return p, v, t
294 return p, v, t
294 if i == 3:
295 if i == 3:
295 return p, q, v
296 return p, q, v
296 if i == 4:
297 if i == 4:
297 return t, p, v
298 return t, p, v
298 if i == 5:
299 if i == 5:
299 return v, p, q
300 return v, p, q
300
301
301 golden_ratio = 0.618033988749895
302 golden_ratio = 0.618033988749895
302 h = 0.22717784590367374
303 h = 0.22717784590367374
303
304
304 for _ in xrange(n):
305 for _ in xrange(n):
305 h += golden_ratio
306 h += golden_ratio
306 h %= 1
307 h %= 1
307 HSV_tuple = [h, 0.95, 0.95]
308 HSV_tuple = [h, 0.95, 0.95]
308 RGB_tuple = hsv_to_rgb(*HSV_tuple)
309 RGB_tuple = hsv_to_rgb(*HSV_tuple)
309 yield map(lambda x: str(int(x * 256)), RGB_tuple)
310 yield map(lambda x: str(int(x * 256)), RGB_tuple)
310
311
311 cgenerator = gen_color()
312 cgenerator = gen_color()
312
313
313 def get_color_string(cs):
314 def get_color_string(cs):
314 if cs in color_dict:
315 if cs in color_dict:
315 col = color_dict[cs]
316 col = color_dict[cs]
316 else:
317 else:
317 col = color_dict[cs] = cgenerator.next()
318 col = color_dict[cs] = cgenerator.next()
318 return "color: rgb(%s)! important;" % (', '.join(col))
319 return "color: rgb(%s)! important;" % (', '.join(col))
319
320
320 def url_func(repo_name):
321 def url_func(repo_name):
321
322
322 def _url_func(changeset):
323 def _url_func(changeset):
323 author = changeset.author
324 author = changeset.author
324 date = changeset.date
325 date = changeset.date
325 message = tooltip(changeset.message)
326 message = tooltip(changeset.message)
326
327
327 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
328 tooltip_html = ("<div style='font-size:0.8em'><b>Author:</b>"
328 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
329 " %s<br/><b>Date:</b> %s</b><br/><b>Message:"
329 "</b> %s<br/></div>")
330 "</b> %s<br/></div>")
330
331
331 tooltip_html = tooltip_html % (author, date, message)
332 tooltip_html = tooltip_html % (author, date, message)
332 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
333 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
333 short_id(changeset.raw_id))
334 short_id(changeset.raw_id))
334 uri = link_to(
335 uri = link_to(
335 lnk_format,
336 lnk_format,
336 url('changeset_home', repo_name=repo_name,
337 url('changeset_home', repo_name=repo_name,
337 revision=changeset.raw_id),
338 revision=changeset.raw_id),
338 style=get_color_string(changeset.raw_id),
339 style=get_color_string(changeset.raw_id),
339 class_='tooltip',
340 class_='tooltip',
340 title=tooltip_html
341 title=tooltip_html
341 )
342 )
342
343
343 uri += '\n'
344 uri += '\n'
344 return uri
345 return uri
345 return _url_func
346 return _url_func
346
347
347 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
348 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
348
349
349
350
350 def is_following_repo(repo_name, user_id):
351 def is_following_repo(repo_name, user_id):
351 from rhodecode.model.scm import ScmModel
352 from rhodecode.model.scm import ScmModel
352 return ScmModel().is_following_repo(repo_name, user_id)
353 return ScmModel().is_following_repo(repo_name, user_id)
353
354
354 flash = _Flash()
355 flash = _Flash()
355
356
356 #==============================================================================
357 #==============================================================================
357 # SCM FILTERS available via h.
358 # SCM FILTERS available via h.
358 #==============================================================================
359 #==============================================================================
359 from rhodecode.lib.vcs.utils import author_name, author_email
360 from rhodecode.lib.vcs.utils import author_name, author_email
360 from rhodecode.lib.utils2 import credentials_filter, age as _age
361 from rhodecode.lib.utils2 import credentials_filter, age as _age
361 from rhodecode.model.db import User, ChangesetStatus
362 from rhodecode.model.db import User, ChangesetStatus
362
363
363 age = lambda x: _age(x)
364 age = lambda x: _age(x)
364 capitalize = lambda x: x.capitalize()
365 capitalize = lambda x: x.capitalize()
365 email = author_email
366 email = author_email
366 short_id = lambda x: x[:12]
367 short_id = lambda x: x[:12]
367 hide_credentials = lambda x: ''.join(credentials_filter(x))
368 hide_credentials = lambda x: ''.join(credentials_filter(x))
368
369
369
370
370 def fmt_date(date):
371 def fmt_date(date):
371 if date:
372 if date:
372 _fmt = _(u"%a, %d %b %Y %H:%M:%S").encode('utf8')
373 _fmt = _(u"%a, %d %b %Y %H:%M:%S").encode('utf8')
373 return date.strftime(_fmt).decode('utf8')
374 return date.strftime(_fmt).decode('utf8')
374
375
375 return ""
376 return ""
376
377
377
378
378 def is_git(repository):
379 def is_git(repository):
379 if hasattr(repository, 'alias'):
380 if hasattr(repository, 'alias'):
380 _type = repository.alias
381 _type = repository.alias
381 elif hasattr(repository, 'repo_type'):
382 elif hasattr(repository, 'repo_type'):
382 _type = repository.repo_type
383 _type = repository.repo_type
383 else:
384 else:
384 _type = repository
385 _type = repository
385 return _type == 'git'
386 return _type == 'git'
386
387
387
388
388 def is_hg(repository):
389 def is_hg(repository):
389 if hasattr(repository, 'alias'):
390 if hasattr(repository, 'alias'):
390 _type = repository.alias
391 _type = repository.alias
391 elif hasattr(repository, 'repo_type'):
392 elif hasattr(repository, 'repo_type'):
392 _type = repository.repo_type
393 _type = repository.repo_type
393 else:
394 else:
394 _type = repository
395 _type = repository
395 return _type == 'hg'
396 return _type == 'hg'
396
397
397
398
398 def email_or_none(author):
399 def email_or_none(author):
399 # extract email from the commit string
400 # extract email from the commit string
400 _email = email(author)
401 _email = email(author)
401 if _email != '':
402 if _email != '':
402 # check it against RhodeCode database, and use the MAIN email for this
403 # check it against RhodeCode database, and use the MAIN email for this
403 # user
404 # user
404 user = User.get_by_email(_email, case_insensitive=True, cache=True)
405 user = User.get_by_email(_email, case_insensitive=True, cache=True)
405 if user is not None:
406 if user is not None:
406 return user.email
407 return user.email
407 return _email
408 return _email
408
409
409 # See if it contains a username we can get an email from
410 # See if it contains a username we can get an email from
410 user = User.get_by_username(author_name(author), case_insensitive=True,
411 user = User.get_by_username(author_name(author), case_insensitive=True,
411 cache=True)
412 cache=True)
412 if user is not None:
413 if user is not None:
413 return user.email
414 return user.email
414
415
415 # No valid email, not a valid user in the system, none!
416 # No valid email, not a valid user in the system, none!
416 return None
417 return None
417
418
418
419
419 def person(author, show_attr="username_and_name"):
420 def person(author, show_attr="username_and_name"):
420 # attr to return from fetched user
421 # attr to return from fetched user
421 person_getter = lambda usr: getattr(usr, show_attr)
422 person_getter = lambda usr: getattr(usr, show_attr)
422
423
423 # Valid email in the attribute passed, see if they're in the system
424 # Valid email in the attribute passed, see if they're in the system
424 _email = email(author)
425 _email = email(author)
425 if _email != '':
426 if _email != '':
426 user = User.get_by_email(_email, case_insensitive=True, cache=True)
427 user = User.get_by_email(_email, case_insensitive=True, cache=True)
427 if user is not None:
428 if user is not None:
428 return person_getter(user)
429 return person_getter(user)
429 return _email
430 return _email
430
431
431 # Maybe it's a username?
432 # Maybe it's a username?
432 _author = author_name(author)
433 _author = author_name(author)
433 user = User.get_by_username(_author, case_insensitive=True,
434 user = User.get_by_username(_author, case_insensitive=True,
434 cache=True)
435 cache=True)
435 if user is not None:
436 if user is not None:
436 return person_getter(user)
437 return person_getter(user)
437
438
438 # Still nothing? Just pass back the author name then
439 # Still nothing? Just pass back the author name then
439 return _author
440 return _author
440
441
441
442
442 def person_by_id(id_, show_attr="username_and_name"):
443 def person_by_id(id_, show_attr="username_and_name"):
443 # attr to return from fetched user
444 # attr to return from fetched user
444 person_getter = lambda usr: getattr(usr, show_attr)
445 person_getter = lambda usr: getattr(usr, show_attr)
445
446
446 #maybe it's an ID ?
447 #maybe it's an ID ?
447 if str(id_).isdigit() or isinstance(id_, int):
448 if str(id_).isdigit() or isinstance(id_, int):
448 id_ = int(id_)
449 id_ = int(id_)
449 user = User.get(id_)
450 user = User.get(id_)
450 if user is not None:
451 if user is not None:
451 return person_getter(user)
452 return person_getter(user)
452 return id_
453 return id_
453
454
454
455
455 def desc_stylize(value):
456 def desc_stylize(value):
456 """
457 """
457 converts tags from value into html equivalent
458 converts tags from value into html equivalent
458
459
459 :param value:
460 :param value:
460 """
461 """
461 value = re.sub(r'\[see\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
462 value = re.sub(r'\[see\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
462 '<div class="metatag" tag="see">see =&gt; \\1 </div>', value)
463 '<div class="metatag" tag="see">see =&gt; \\1 </div>', value)
463 value = re.sub(r'\[license\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
464 value = re.sub(r'\[license\ \=\>\ *([a-zA-Z0-9\/\=\?\&\ \:\/\.\-]*)\]',
464 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>', value)
465 '<div class="metatag" tag="license"><a href="http:\/\/www.opensource.org/licenses/\\1">\\1</a></div>', value)
465 value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=\>\ *([a-zA-Z\-\/]*)\]',
466 value = re.sub(r'\[(requires|recommends|conflicts|base)\ \=\>\ *([a-zA-Z\-\/]*)\]',
466 '<div class="metatag" tag="\\1">\\1 =&gt; <a href="/\\2">\\2</a></div>', value)
467 '<div class="metatag" tag="\\1">\\1 =&gt; <a href="/\\2">\\2</a></div>', value)
467 value = re.sub(r'\[(lang|language)\ \=\>\ *([a-zA-Z\-\/]*)\]',
468 value = re.sub(r'\[(lang|language)\ \=\>\ *([a-zA-Z\-\/]*)\]',
468 '<div class="metatag" tag="lang">\\2</div>', value)
469 '<div class="metatag" tag="lang">\\2</div>', value)
469 value = re.sub(r'\[([a-z]+)\]',
470 value = re.sub(r'\[([a-z]+)\]',
470 '<div class="metatag" tag="\\1">\\1</div>', value)
471 '<div class="metatag" tag="\\1">\\1</div>', value)
471
472
472 return value
473 return value
473
474
474
475
475 def bool2icon(value):
476 def bool2icon(value):
476 """Returns True/False values represented as small html image of true/false
477 """Returns True/False values represented as small html image of true/false
477 icons
478 icons
478
479
479 :param value: bool value
480 :param value: bool value
480 """
481 """
481
482
482 if value is True:
483 if value is True:
483 return HTML.tag('img', src=url("/images/icons/accept.png"),
484 return HTML.tag('img', src=url("/images/icons/accept.png"),
484 alt=_('True'))
485 alt=_('True'))
485
486
486 if value is False:
487 if value is False:
487 return HTML.tag('img', src=url("/images/icons/cancel.png"),
488 return HTML.tag('img', src=url("/images/icons/cancel.png"),
488 alt=_('False'))
489 alt=_('False'))
489
490
490 return value
491 return value
491
492
492
493
493 def action_parser(user_log, feed=False):
494 def action_parser(user_log, feed=False):
494 """
495 """
495 This helper will action_map the specified string action into translated
496 This helper will action_map the specified string action into translated
496 fancy names with icons and links
497 fancy names with icons and links
497
498
498 :param user_log: user log instance
499 :param user_log: user log instance
499 :param feed: use output for feeds (no html and fancy icons)
500 :param feed: use output for feeds (no html and fancy icons)
500 """
501 """
501
502
502 action = user_log.action
503 action = user_log.action
503 action_params = ' '
504 action_params = ' '
504
505
505 x = action.split(':')
506 x = action.split(':')
506
507
507 if len(x) > 1:
508 if len(x) > 1:
508 action, action_params = x
509 action, action_params = x
509
510
510 def get_cs_links():
511 def get_cs_links():
511 revs_limit = 3 # display this amount always
512 revs_limit = 3 # display this amount always
512 revs_top_limit = 50 # show upto this amount of changesets hidden
513 revs_top_limit = 50 # show upto this amount of changesets hidden
513 revs_ids = action_params.split(',')
514 revs_ids = action_params.split(',')
514 deleted = user_log.repository is None
515 deleted = user_log.repository is None
515 if deleted:
516 if deleted:
516 return ','.join(revs_ids)
517 return ','.join(revs_ids)
517
518
518 repo_name = user_log.repository.repo_name
519 repo_name = user_log.repository.repo_name
519
520
520 repo = user_log.repository.scm_instance
521 repo = user_log.repository.scm_instance
521
522
522 def lnk(rev, repo_name):
523 def lnk(rev, repo_name):
523
524
524 if isinstance(rev, BaseChangeset):
525 if isinstance(rev, BaseChangeset):
525 lbl = 'r%s:%s' % (rev.revision, rev.short_id)
526 lbl = 'r%s:%s' % (rev.revision, rev.short_id)
526 _url = url('changeset_home', repo_name=repo_name,
527 _url = url('changeset_home', repo_name=repo_name,
527 revision=rev.raw_id)
528 revision=rev.raw_id)
528 title = tooltip(rev.message)
529 title = tooltip(rev.message)
529 else:
530 else:
530 lbl = '%s' % rev
531 lbl = '%s' % rev
531 _url = '#'
532 _url = '#'
532 title = _('Changeset not found')
533 title = _('Changeset not found')
533
534
534 return link_to(lbl, _url, title=title, class_='tooltip',)
535 return link_to(lbl, _url, title=title, class_='tooltip',)
535
536
536 revs = []
537 revs = []
537 if len(filter(lambda v: v != '', revs_ids)) > 0:
538 if len(filter(lambda v: v != '', revs_ids)) > 0:
538 for rev in revs_ids[:revs_top_limit]:
539 for rev in revs_ids[:revs_top_limit]:
539 try:
540 try:
540 rev = repo.get_changeset(rev)
541 rev = repo.get_changeset(rev)
541 revs.append(rev)
542 revs.append(rev)
542 except ChangesetDoesNotExistError:
543 except ChangesetDoesNotExistError:
543 log.error('cannot find revision %s in this repo' % rev)
544 log.error('cannot find revision %s in this repo' % rev)
544 revs.append(rev)
545 revs.append(rev)
545 continue
546 continue
546 cs_links = []
547 cs_links = []
547 cs_links.append(" " + ', '.join(
548 cs_links.append(" " + ', '.join(
548 [lnk(rev, repo_name) for rev in revs[:revs_limit]]
549 [lnk(rev, repo_name) for rev in revs[:revs_limit]]
549 )
550 )
550 )
551 )
551
552
552 compare_view = (
553 compare_view = (
553 ' <div class="compare_view tooltip" title="%s">'
554 ' <div class="compare_view tooltip" title="%s">'
554 '<a href="%s">%s</a> </div>' % (
555 '<a href="%s">%s</a> </div>' % (
555 _('Show all combined changesets %s->%s') % (
556 _('Show all combined changesets %s->%s') % (
556 revs_ids[0], revs_ids[-1]
557 revs_ids[0], revs_ids[-1]
557 ),
558 ),
558 url('changeset_home', repo_name=repo_name,
559 url('changeset_home', repo_name=repo_name,
559 revision='%s...%s' % (revs_ids[0], revs_ids[-1])
560 revision='%s...%s' % (revs_ids[0], revs_ids[-1])
560 ),
561 ),
561 _('compare view')
562 _('compare view')
562 )
563 )
563 )
564 )
564
565
565 # if we have exactly one more than normally displayed
566 # if we have exactly one more than normally displayed
566 # just display it, takes less space than displaying
567 # just display it, takes less space than displaying
567 # "and 1 more revisions"
568 # "and 1 more revisions"
568 if len(revs_ids) == revs_limit + 1:
569 if len(revs_ids) == revs_limit + 1:
569 rev = revs[revs_limit]
570 rev = revs[revs_limit]
570 cs_links.append(", " + lnk(rev, repo_name))
571 cs_links.append(", " + lnk(rev, repo_name))
571
572
572 # hidden-by-default ones
573 # hidden-by-default ones
573 if len(revs_ids) > revs_limit + 1:
574 if len(revs_ids) > revs_limit + 1:
574 uniq_id = revs_ids[0]
575 uniq_id = revs_ids[0]
575 html_tmpl = (
576 html_tmpl = (
576 '<span> %s <a class="show_more" id="_%s" '
577 '<span> %s <a class="show_more" id="_%s" '
577 'href="#more">%s</a> %s</span>'
578 'href="#more">%s</a> %s</span>'
578 )
579 )
579 if not feed:
580 if not feed:
580 cs_links.append(html_tmpl % (
581 cs_links.append(html_tmpl % (
581 _('and'),
582 _('and'),
582 uniq_id, _('%s more') % (len(revs_ids) - revs_limit),
583 uniq_id, _('%s more') % (len(revs_ids) - revs_limit),
583 _('revisions')
584 _('revisions')
584 )
585 )
585 )
586 )
586
587
587 if not feed:
588 if not feed:
588 html_tmpl = '<span id="%s" style="display:none">, %s </span>'
589 html_tmpl = '<span id="%s" style="display:none">, %s </span>'
589 else:
590 else:
590 html_tmpl = '<span id="%s"> %s </span>'
591 html_tmpl = '<span id="%s"> %s </span>'
591
592
592 morelinks = ', '.join(
593 morelinks = ', '.join(
593 [lnk(rev, repo_name) for rev in revs[revs_limit:]]
594 [lnk(rev, repo_name) for rev in revs[revs_limit:]]
594 )
595 )
595
596
596 if len(revs_ids) > revs_top_limit:
597 if len(revs_ids) > revs_top_limit:
597 morelinks += ', ...'
598 morelinks += ', ...'
598
599
599 cs_links.append(html_tmpl % (uniq_id, morelinks))
600 cs_links.append(html_tmpl % (uniq_id, morelinks))
600 if len(revs) > 1:
601 if len(revs) > 1:
601 cs_links.append(compare_view)
602 cs_links.append(compare_view)
602 return ''.join(cs_links)
603 return ''.join(cs_links)
603
604
604 def get_fork_name():
605 def get_fork_name():
605 repo_name = action_params
606 repo_name = action_params
606 return _('fork name ') + str(link_to(action_params, url('summary_home',
607 return _('fork name ') + str(link_to(action_params, url('summary_home',
607 repo_name=repo_name,)))
608 repo_name=repo_name,)))
608
609
609 def get_user_name():
610 def get_user_name():
610 user_name = action_params
611 user_name = action_params
611 return user_name
612 return user_name
612
613
613 def get_users_group():
614 def get_users_group():
614 group_name = action_params
615 group_name = action_params
615 return group_name
616 return group_name
616
617
617 def get_pull_request():
618 def get_pull_request():
618 pull_request_id = action_params
619 pull_request_id = action_params
619 repo_name = user_log.repository.repo_name
620 repo_name = user_log.repository.repo_name
620 return link_to(_('Pull request #%s') % pull_request_id,
621 return link_to(_('Pull request #%s') % pull_request_id,
621 url('pullrequest_show', repo_name=repo_name,
622 url('pullrequest_show', repo_name=repo_name,
622 pull_request_id=pull_request_id))
623 pull_request_id=pull_request_id))
623
624
624 # action : translated str, callback(extractor), icon
625 # action : translated str, callback(extractor), icon
625 action_map = {
626 action_map = {
626 'user_deleted_repo': (_('[deleted] repository'),
627 'user_deleted_repo': (_('[deleted] repository'),
627 None, 'database_delete.png'),
628 None, 'database_delete.png'),
628 'user_created_repo': (_('[created] repository'),
629 'user_created_repo': (_('[created] repository'),
629 None, 'database_add.png'),
630 None, 'database_add.png'),
630 'user_created_fork': (_('[created] repository as fork'),
631 'user_created_fork': (_('[created] repository as fork'),
631 None, 'arrow_divide.png'),
632 None, 'arrow_divide.png'),
632 'user_forked_repo': (_('[forked] repository'),
633 'user_forked_repo': (_('[forked] repository'),
633 get_fork_name, 'arrow_divide.png'),
634 get_fork_name, 'arrow_divide.png'),
634 'user_updated_repo': (_('[updated] repository'),
635 'user_updated_repo': (_('[updated] repository'),
635 None, 'database_edit.png'),
636 None, 'database_edit.png'),
636 'admin_deleted_repo': (_('[delete] repository'),
637 'admin_deleted_repo': (_('[delete] repository'),
637 None, 'database_delete.png'),
638 None, 'database_delete.png'),
638 'admin_created_repo': (_('[created] repository'),
639 'admin_created_repo': (_('[created] repository'),
639 None, 'database_add.png'),
640 None, 'database_add.png'),
640 'admin_forked_repo': (_('[forked] repository'),
641 'admin_forked_repo': (_('[forked] repository'),
641 None, 'arrow_divide.png'),
642 None, 'arrow_divide.png'),
642 'admin_updated_repo': (_('[updated] repository'),
643 'admin_updated_repo': (_('[updated] repository'),
643 None, 'database_edit.png'),
644 None, 'database_edit.png'),
644 'admin_created_user': (_('[created] user'),
645 'admin_created_user': (_('[created] user'),
645 get_user_name, 'user_add.png'),
646 get_user_name, 'user_add.png'),
646 'admin_updated_user': (_('[updated] user'),
647 'admin_updated_user': (_('[updated] user'),
647 get_user_name, 'user_edit.png'),
648 get_user_name, 'user_edit.png'),
648 'admin_created_users_group': (_('[created] users group'),
649 'admin_created_users_group': (_('[created] users group'),
649 get_users_group, 'group_add.png'),
650 get_users_group, 'group_add.png'),
650 'admin_updated_users_group': (_('[updated] users group'),
651 'admin_updated_users_group': (_('[updated] users group'),
651 get_users_group, 'group_edit.png'),
652 get_users_group, 'group_edit.png'),
652 'user_commented_revision': (_('[commented] on revision in repository'),
653 'user_commented_revision': (_('[commented] on revision in repository'),
653 get_cs_links, 'comment_add.png'),
654 get_cs_links, 'comment_add.png'),
654 'user_commented_pull_request': (_('[commented] on pull request for'),
655 'user_commented_pull_request': (_('[commented] on pull request for'),
655 get_pull_request, 'comment_add.png'),
656 get_pull_request, 'comment_add.png'),
656 'user_closed_pull_request': (_('[closed] pull request for'),
657 'user_closed_pull_request': (_('[closed] pull request for'),
657 get_pull_request, 'tick.png'),
658 get_pull_request, 'tick.png'),
658 'push': (_('[pushed] into'),
659 'push': (_('[pushed] into'),
659 get_cs_links, 'script_add.png'),
660 get_cs_links, 'script_add.png'),
660 'push_local': (_('[committed via RhodeCode] into repository'),
661 'push_local': (_('[committed via RhodeCode] into repository'),
661 get_cs_links, 'script_edit.png'),
662 get_cs_links, 'script_edit.png'),
662 'push_remote': (_('[pulled from remote] into repository'),
663 'push_remote': (_('[pulled from remote] into repository'),
663 get_cs_links, 'connect.png'),
664 get_cs_links, 'connect.png'),
664 'pull': (_('[pulled] from'),
665 'pull': (_('[pulled] from'),
665 None, 'down_16.png'),
666 None, 'down_16.png'),
666 'started_following_repo': (_('[started following] repository'),
667 'started_following_repo': (_('[started following] repository'),
667 None, 'heart_add.png'),
668 None, 'heart_add.png'),
668 'stopped_following_repo': (_('[stopped following] repository'),
669 'stopped_following_repo': (_('[stopped following] repository'),
669 None, 'heart_delete.png'),
670 None, 'heart_delete.png'),
670 }
671 }
671
672
672 action_str = action_map.get(action, action)
673 action_str = action_map.get(action, action)
673 if feed:
674 if feed:
674 action = action_str[0].replace('[', '').replace(']', '')
675 action = action_str[0].replace('[', '').replace(']', '')
675 else:
676 else:
676 action = action_str[0]\
677 action = action_str[0]\
677 .replace('[', '<span class="journal_highlight">')\
678 .replace('[', '<span class="journal_highlight">')\
678 .replace(']', '</span>')
679 .replace(']', '</span>')
679
680
680 action_params_func = lambda: ""
681 action_params_func = lambda: ""
681
682
682 if callable(action_str[1]):
683 if callable(action_str[1]):
683 action_params_func = action_str[1]
684 action_params_func = action_str[1]
684
685
685 def action_parser_icon():
686 def action_parser_icon():
686 action = user_log.action
687 action = user_log.action
687 action_params = None
688 action_params = None
688 x = action.split(':')
689 x = action.split(':')
689
690
690 if len(x) > 1:
691 if len(x) > 1:
691 action, action_params = x
692 action, action_params = x
692
693
693 tmpl = """<img src="%s%s" alt="%s"/>"""
694 tmpl = """<img src="%s%s" alt="%s"/>"""
694 ico = action_map.get(action, ['', '', ''])[2]
695 ico = action_map.get(action, ['', '', ''])[2]
695 return literal(tmpl % ((url('/images/icons/')), ico, action))
696 return literal(tmpl % ((url('/images/icons/')), ico, action))
696
697
697 # returned callbacks we need to call to get
698 # returned callbacks we need to call to get
698 return [lambda: literal(action), action_params_func, action_parser_icon]
699 return [lambda: literal(action), action_params_func, action_parser_icon]
699
700
700
701
701
702
702 #==============================================================================
703 #==============================================================================
703 # PERMS
704 # PERMS
704 #==============================================================================
705 #==============================================================================
705 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
706 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
706 HasRepoPermissionAny, HasRepoPermissionAll
707 HasRepoPermissionAny, HasRepoPermissionAll
707
708
708
709
709 #==============================================================================
710 #==============================================================================
710 # GRAVATAR URL
711 # GRAVATAR URL
711 #==============================================================================
712 #==============================================================================
712
713
713 def gravatar_url(email_address, size=30):
714 def gravatar_url(email_address, size=30):
715 from pylons import url ## doh, we need to re-import url to mock it later
714 if(str2bool(config['app_conf'].get('use_gravatar')) and
716 if(str2bool(config['app_conf'].get('use_gravatar')) and
715 config['app_conf'].get('alternative_gravatar_url')):
717 config['app_conf'].get('alternative_gravatar_url')):
716 tmpl = config['app_conf'].get('alternative_gravatar_url', '')
718 tmpl = config['app_conf'].get('alternative_gravatar_url', '')
719 parsed_url = urlparse.urlparse(url.current(qualified=True))
717 tmpl = tmpl.replace('{email}', email_address)\
720 tmpl = tmpl.replace('{email}', email_address)\
718 .replace('{md5email}', hashlib.md5(email_address.lower()).hexdigest())\
721 .replace('{md5email}', hashlib.md5(email_address.lower()).hexdigest()) \
722 .replace('{netloc}', parsed_url.netloc)\
723 .replace('{scheme}', parsed_url.scheme)\
719 .replace('{size}', str(size))
724 .replace('{size}', str(size))
720 return tmpl
725 return tmpl
721
726
722 if (not str2bool(config['app_conf'].get('use_gravatar')) or
727 if (not str2bool(config['app_conf'].get('use_gravatar')) or
723 not email_address or email_address == 'anonymous@rhodecode.org'):
728 not email_address or email_address == 'anonymous@rhodecode.org'):
724 f = lambda a, l: min(l, key=lambda x: abs(x - a))
729 f = lambda a, l: min(l, key=lambda x: abs(x - a))
725 return url("/images/user%s.png" % f(size, [14, 16, 20, 24, 30]))
730 return url("/images/user%s.png" % f(size, [14, 16, 20, 24, 30]))
726
731
727 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
732 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
728 default = 'identicon'
733 default = 'identicon'
729 baseurl_nossl = "http://www.gravatar.com/avatar/"
734 baseurl_nossl = "http://www.gravatar.com/avatar/"
730 baseurl_ssl = "https://secure.gravatar.com/avatar/"
735 baseurl_ssl = "https://secure.gravatar.com/avatar/"
731 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
736 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
732
737
733 if isinstance(email_address, unicode):
738 if isinstance(email_address, unicode):
734 #hashlib crashes on unicode items
739 #hashlib crashes on unicode items
735 email_address = safe_str(email_address)
740 email_address = safe_str(email_address)
736 # construct the url
741 # construct the url
737 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
742 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
738 gravatar_url += urllib.urlencode({'d': default, 's': str(size)})
743 gravatar_url += urllib.urlencode({'d': default, 's': str(size)})
739
744
740 return gravatar_url
745 return gravatar_url
741
746
742
747
743 #==============================================================================
748 #==============================================================================
744 # REPO PAGER, PAGER FOR REPOSITORY
749 # REPO PAGER, PAGER FOR REPOSITORY
745 #==============================================================================
750 #==============================================================================
746 class RepoPage(Page):
751 class RepoPage(Page):
747
752
748 def __init__(self, collection, page=1, items_per_page=20,
753 def __init__(self, collection, page=1, items_per_page=20,
749 item_count=None, url=None, **kwargs):
754 item_count=None, url=None, **kwargs):
750
755
751 """Create a "RepoPage" instance. special pager for paging
756 """Create a "RepoPage" instance. special pager for paging
752 repository
757 repository
753 """
758 """
754 self._url_generator = url
759 self._url_generator = url
755
760
756 # Safe the kwargs class-wide so they can be used in the pager() method
761 # Safe the kwargs class-wide so they can be used in the pager() method
757 self.kwargs = kwargs
762 self.kwargs = kwargs
758
763
759 # Save a reference to the collection
764 # Save a reference to the collection
760 self.original_collection = collection
765 self.original_collection = collection
761
766
762 self.collection = collection
767 self.collection = collection
763
768
764 # The self.page is the number of the current page.
769 # The self.page is the number of the current page.
765 # The first page has the number 1!
770 # The first page has the number 1!
766 try:
771 try:
767 self.page = int(page) # make it int() if we get it as a string
772 self.page = int(page) # make it int() if we get it as a string
768 except (ValueError, TypeError):
773 except (ValueError, TypeError):
769 self.page = 1
774 self.page = 1
770
775
771 self.items_per_page = items_per_page
776 self.items_per_page = items_per_page
772
777
773 # Unless the user tells us how many items the collections has
778 # Unless the user tells us how many items the collections has
774 # we calculate that ourselves.
779 # we calculate that ourselves.
775 if item_count is not None:
780 if item_count is not None:
776 self.item_count = item_count
781 self.item_count = item_count
777 else:
782 else:
778 self.item_count = len(self.collection)
783 self.item_count = len(self.collection)
779
784
780 # Compute the number of the first and last available page
785 # Compute the number of the first and last available page
781 if self.item_count > 0:
786 if self.item_count > 0:
782 self.first_page = 1
787 self.first_page = 1
783 self.page_count = int(math.ceil(float(self.item_count) /
788 self.page_count = int(math.ceil(float(self.item_count) /
784 self.items_per_page))
789 self.items_per_page))
785 self.last_page = self.first_page + self.page_count - 1
790 self.last_page = self.first_page + self.page_count - 1
786
791
787 # Make sure that the requested page number is the range of
792 # Make sure that the requested page number is the range of
788 # valid pages
793 # valid pages
789 if self.page > self.last_page:
794 if self.page > self.last_page:
790 self.page = self.last_page
795 self.page = self.last_page
791 elif self.page < self.first_page:
796 elif self.page < self.first_page:
792 self.page = self.first_page
797 self.page = self.first_page
793
798
794 # Note: the number of items on this page can be less than
799 # Note: the number of items on this page can be less than
795 # items_per_page if the last page is not full
800 # items_per_page if the last page is not full
796 self.first_item = max(0, (self.item_count) - (self.page *
801 self.first_item = max(0, (self.item_count) - (self.page *
797 items_per_page))
802 items_per_page))
798 self.last_item = ((self.item_count - 1) - items_per_page *
803 self.last_item = ((self.item_count - 1) - items_per_page *
799 (self.page - 1))
804 (self.page - 1))
800
805
801 self.items = list(self.collection[self.first_item:self.last_item + 1])
806 self.items = list(self.collection[self.first_item:self.last_item + 1])
802
807
803 # Links to previous and next page
808 # Links to previous and next page
804 if self.page > self.first_page:
809 if self.page > self.first_page:
805 self.previous_page = self.page - 1
810 self.previous_page = self.page - 1
806 else:
811 else:
807 self.previous_page = None
812 self.previous_page = None
808
813
809 if self.page < self.last_page:
814 if self.page < self.last_page:
810 self.next_page = self.page + 1
815 self.next_page = self.page + 1
811 else:
816 else:
812 self.next_page = None
817 self.next_page = None
813
818
814 # No items available
819 # No items available
815 else:
820 else:
816 self.first_page = None
821 self.first_page = None
817 self.page_count = 0
822 self.page_count = 0
818 self.last_page = None
823 self.last_page = None
819 self.first_item = None
824 self.first_item = None
820 self.last_item = None
825 self.last_item = None
821 self.previous_page = None
826 self.previous_page = None
822 self.next_page = None
827 self.next_page = None
823 self.items = []
828 self.items = []
824
829
825 # This is a subclass of the 'list' type. Initialise the list now.
830 # This is a subclass of the 'list' type. Initialise the list now.
826 list.__init__(self, reversed(self.items))
831 list.__init__(self, reversed(self.items))
827
832
828
833
829 def changed_tooltip(nodes):
834 def changed_tooltip(nodes):
830 """
835 """
831 Generates a html string for changed nodes in changeset page.
836 Generates a html string for changed nodes in changeset page.
832 It limits the output to 30 entries
837 It limits the output to 30 entries
833
838
834 :param nodes: LazyNodesGenerator
839 :param nodes: LazyNodesGenerator
835 """
840 """
836 if nodes:
841 if nodes:
837 pref = ': <br/> '
842 pref = ': <br/> '
838 suf = ''
843 suf = ''
839 if len(nodes) > 30:
844 if len(nodes) > 30:
840 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
845 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
841 return literal(pref + '<br/> '.join([safe_unicode(x.path)
846 return literal(pref + '<br/> '.join([safe_unicode(x.path)
842 for x in nodes[:30]]) + suf)
847 for x in nodes[:30]]) + suf)
843 else:
848 else:
844 return ': ' + _('No Files')
849 return ': ' + _('No Files')
845
850
846
851
847 def repo_link(groups_and_repos):
852 def repo_link(groups_and_repos):
848 """
853 """
849 Makes a breadcrumbs link to repo within a group
854 Makes a breadcrumbs link to repo within a group
850 joins &raquo; on each group to create a fancy link
855 joins &raquo; on each group to create a fancy link
851
856
852 ex::
857 ex::
853 group >> subgroup >> repo
858 group >> subgroup >> repo
854
859
855 :param groups_and_repos:
860 :param groups_and_repos:
856 """
861 """
857 groups, repo_name = groups_and_repos
862 groups, repo_name = groups_and_repos
858
863
859 if not groups:
864 if not groups:
860 return repo_name
865 return repo_name
861 else:
866 else:
862 def make_link(group):
867 def make_link(group):
863 return link_to(group.name, url('repos_group_home',
868 return link_to(group.name, url('repos_group_home',
864 group_name=group.group_name))
869 group_name=group.group_name))
865 return literal(' &raquo; '.join(map(make_link, groups)) + \
870 return literal(' &raquo; '.join(map(make_link, groups)) + \
866 " &raquo; " + repo_name)
871 " &raquo; " + repo_name)
867
872
868
873
869 def fancy_file_stats(stats):
874 def fancy_file_stats(stats):
870 """
875 """
871 Displays a fancy two colored bar for number of added/deleted
876 Displays a fancy two colored bar for number of added/deleted
872 lines of code on file
877 lines of code on file
873
878
874 :param stats: two element list of added/deleted lines of code
879 :param stats: two element list of added/deleted lines of code
875 """
880 """
876
881
877 a, d, t = stats[0], stats[1], stats[0] + stats[1]
882 a, d, t = stats[0], stats[1], stats[0] + stats[1]
878 width = 100
883 width = 100
879 unit = float(width) / (t or 1)
884 unit = float(width) / (t or 1)
880
885
881 # needs > 9% of width to be visible or 0 to be hidden
886 # needs > 9% of width to be visible or 0 to be hidden
882 a_p = max(9, unit * a) if a > 0 else 0
887 a_p = max(9, unit * a) if a > 0 else 0
883 d_p = max(9, unit * d) if d > 0 else 0
888 d_p = max(9, unit * d) if d > 0 else 0
884 p_sum = a_p + d_p
889 p_sum = a_p + d_p
885
890
886 if p_sum > width:
891 if p_sum > width:
887 #adjust the percentage to be == 100% since we adjusted to 9
892 #adjust the percentage to be == 100% since we adjusted to 9
888 if a_p > d_p:
893 if a_p > d_p:
889 a_p = a_p - (p_sum - width)
894 a_p = a_p - (p_sum - width)
890 else:
895 else:
891 d_p = d_p - (p_sum - width)
896 d_p = d_p - (p_sum - width)
892
897
893 a_v = a if a > 0 else ''
898 a_v = a if a > 0 else ''
894 d_v = d if d > 0 else ''
899 d_v = d if d > 0 else ''
895
900
896 def cgen(l_type):
901 def cgen(l_type):
897 mapping = {'tr': 'top-right-rounded-corner-mid',
902 mapping = {'tr': 'top-right-rounded-corner-mid',
898 'tl': 'top-left-rounded-corner-mid',
903 'tl': 'top-left-rounded-corner-mid',
899 'br': 'bottom-right-rounded-corner-mid',
904 'br': 'bottom-right-rounded-corner-mid',
900 'bl': 'bottom-left-rounded-corner-mid'}
905 'bl': 'bottom-left-rounded-corner-mid'}
901 map_getter = lambda x: mapping[x]
906 map_getter = lambda x: mapping[x]
902
907
903 if l_type == 'a' and d_v:
908 if l_type == 'a' and d_v:
904 #case when added and deleted are present
909 #case when added and deleted are present
905 return ' '.join(map(map_getter, ['tl', 'bl']))
910 return ' '.join(map(map_getter, ['tl', 'bl']))
906
911
907 if l_type == 'a' and not d_v:
912 if l_type == 'a' and not d_v:
908 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
913 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
909
914
910 if l_type == 'd' and a_v:
915 if l_type == 'd' and a_v:
911 return ' '.join(map(map_getter, ['tr', 'br']))
916 return ' '.join(map(map_getter, ['tr', 'br']))
912
917
913 if l_type == 'd' and not a_v:
918 if l_type == 'd' and not a_v:
914 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
919 return ' '.join(map(map_getter, ['tr', 'br', 'tl', 'bl']))
915
920
916 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (
921 d_a = '<div class="added %s" style="width:%s%%">%s</div>' % (
917 cgen('a'), a_p, a_v
922 cgen('a'), a_p, a_v
918 )
923 )
919 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (
924 d_d = '<div class="deleted %s" style="width:%s%%">%s</div>' % (
920 cgen('d'), d_p, d_v
925 cgen('d'), d_p, d_v
921 )
926 )
922 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
927 return literal('<div style="width:%spx">%s%s</div>' % (width, d_a, d_d))
923
928
924
929
925 def urlify_text(text_):
930 def urlify_text(text_):
926 import re
931 import re
927
932
928 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]'''
933 url_pat = re.compile(r'''(http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]'''
929 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
934 '''|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+)''')
930
935
931 def url_func(match_obj):
936 def url_func(match_obj):
932 url_full = match_obj.groups()[0]
937 url_full = match_obj.groups()[0]
933 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
938 return '<a href="%(url)s">%(url)s</a>' % ({'url': url_full})
934
939
935 return literal(url_pat.sub(url_func, text_))
940 return literal(url_pat.sub(url_func, text_))
936
941
937
942
938 def urlify_changesets(text_, repository):
943 def urlify_changesets(text_, repository):
939 """
944 """
940 Extract revision ids from changeset and make link from them
945 Extract revision ids from changeset and make link from them
941
946
942 :param text_:
947 :param text_:
943 :param repository:
948 :param repository:
944 """
949 """
945 import re
950 import re
946 URL_PAT = re.compile(r'([0-9a-fA-F]{12,})')
951 URL_PAT = re.compile(r'([0-9a-fA-F]{12,})')
947
952
948 def url_func(match_obj):
953 def url_func(match_obj):
949 rev = match_obj.groups()[0]
954 rev = match_obj.groups()[0]
950 pref = ''
955 pref = ''
951 if match_obj.group().startswith(' '):
956 if match_obj.group().startswith(' '):
952 pref = ' '
957 pref = ' '
953 tmpl = (
958 tmpl = (
954 '%(pref)s<a class="%(cls)s" href="%(url)s">'
959 '%(pref)s<a class="%(cls)s" href="%(url)s">'
955 '%(rev)s'
960 '%(rev)s'
956 '</a>'
961 '</a>'
957 )
962 )
958 return tmpl % {
963 return tmpl % {
959 'pref': pref,
964 'pref': pref,
960 'cls': 'revision-link',
965 'cls': 'revision-link',
961 'url': url('changeset_home', repo_name=repository, revision=rev),
966 'url': url('changeset_home', repo_name=repository, revision=rev),
962 'rev': rev,
967 'rev': rev,
963 }
968 }
964
969
965 newtext = URL_PAT.sub(url_func, text_)
970 newtext = URL_PAT.sub(url_func, text_)
966
971
967 return newtext
972 return newtext
968
973
969
974
970 def urlify_commit(text_, repository=None, link_=None):
975 def urlify_commit(text_, repository=None, link_=None):
971 """
976 """
972 Parses given text message and makes proper links.
977 Parses given text message and makes proper links.
973 issues are linked to given issue-server, and rest is a changeset link
978 issues are linked to given issue-server, and rest is a changeset link
974 if link_ is given, in other case it's a plain text
979 if link_ is given, in other case it's a plain text
975
980
976 :param text_:
981 :param text_:
977 :param repository:
982 :param repository:
978 :param link_: changeset link
983 :param link_: changeset link
979 """
984 """
980 import re
985 import re
981 import traceback
986 import traceback
982
987
983 def escaper(string):
988 def escaper(string):
984 return string.replace('<', '&lt;').replace('>', '&gt;')
989 return string.replace('<', '&lt;').replace('>', '&gt;')
985
990
986 def linkify_others(t, l):
991 def linkify_others(t, l):
987 urls = re.compile(r'(\<a.*?\<\/a\>)',)
992 urls = re.compile(r'(\<a.*?\<\/a\>)',)
988 links = []
993 links = []
989 for e in urls.split(t):
994 for e in urls.split(t):
990 if not urls.match(e):
995 if not urls.match(e):
991 links.append('<a class="message-link" href="%s">%s</a>' % (l, e))
996 links.append('<a class="message-link" href="%s">%s</a>' % (l, e))
992 else:
997 else:
993 links.append(e)
998 links.append(e)
994
999
995 return ''.join(links)
1000 return ''.join(links)
996
1001
997 # urlify changesets - extrac revisions and make link out of them
1002 # urlify changesets - extrac revisions and make link out of them
998 text_ = urlify_changesets(escaper(text_), repository)
1003 text_ = urlify_changesets(escaper(text_), repository)
999
1004
1000 try:
1005 try:
1001 conf = config['app_conf']
1006 conf = config['app_conf']
1002
1007
1003 URL_PAT = re.compile(r'%s' % conf.get('issue_pat'))
1008 URL_PAT = re.compile(r'%s' % conf.get('issue_pat'))
1004
1009
1005 if URL_PAT:
1010 if URL_PAT:
1006 ISSUE_SERVER_LNK = conf.get('issue_server_link')
1011 ISSUE_SERVER_LNK = conf.get('issue_server_link')
1007 ISSUE_PREFIX = conf.get('issue_prefix')
1012 ISSUE_PREFIX = conf.get('issue_prefix')
1008
1013
1009 def url_func(match_obj):
1014 def url_func(match_obj):
1010 pref = ''
1015 pref = ''
1011 if match_obj.group().startswith(' '):
1016 if match_obj.group().startswith(' '):
1012 pref = ' '
1017 pref = ' '
1013
1018
1014 issue_id = ''.join(match_obj.groups())
1019 issue_id = ''.join(match_obj.groups())
1015 tmpl = (
1020 tmpl = (
1016 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1021 '%(pref)s<a class="%(cls)s" href="%(url)s">'
1017 '%(issue-prefix)s%(id-repr)s'
1022 '%(issue-prefix)s%(id-repr)s'
1018 '</a>'
1023 '</a>'
1019 )
1024 )
1020 url = ISSUE_SERVER_LNK.replace('{id}', issue_id)
1025 url = ISSUE_SERVER_LNK.replace('{id}', issue_id)
1021 if repository:
1026 if repository:
1022 url = url.replace('{repo}', repository)
1027 url = url.replace('{repo}', repository)
1023 repo_name = repository.split(URL_SEP)[-1]
1028 repo_name = repository.split(URL_SEP)[-1]
1024 url = url.replace('{repo_name}', repo_name)
1029 url = url.replace('{repo_name}', repo_name)
1025 return tmpl % {
1030 return tmpl % {
1026 'pref': pref,
1031 'pref': pref,
1027 'cls': 'issue-tracker-link',
1032 'cls': 'issue-tracker-link',
1028 'url': url,
1033 'url': url,
1029 'id-repr': issue_id,
1034 'id-repr': issue_id,
1030 'issue-prefix': ISSUE_PREFIX,
1035 'issue-prefix': ISSUE_PREFIX,
1031 'serv': ISSUE_SERVER_LNK,
1036 'serv': ISSUE_SERVER_LNK,
1032 }
1037 }
1033
1038
1034 newtext = URL_PAT.sub(url_func, text_)
1039 newtext = URL_PAT.sub(url_func, text_)
1035
1040
1036 if link_:
1041 if link_:
1037 # wrap not links into final link => link_
1042 # wrap not links into final link => link_
1038 newtext = linkify_others(newtext, link_)
1043 newtext = linkify_others(newtext, link_)
1039
1044
1040 return literal(newtext)
1045 return literal(newtext)
1041 except:
1046 except:
1042 log.error(traceback.format_exc())
1047 log.error(traceback.format_exc())
1043 pass
1048 pass
1044
1049
1045 return text_
1050 return text_
1046
1051
1047
1052
1048 def rst(source):
1053 def rst(source):
1049 return literal('<div class="rst-block">%s</div>' %
1054 return literal('<div class="rst-block">%s</div>' %
1050 MarkupRenderer.rst(source))
1055 MarkupRenderer.rst(source))
1051
1056
1052
1057
1053 def rst_w_mentions(source):
1058 def rst_w_mentions(source):
1054 """
1059 """
1055 Wrapped rst renderer with @mention highlighting
1060 Wrapped rst renderer with @mention highlighting
1056
1061
1057 :param source:
1062 :param source:
1058 """
1063 """
1059 return literal('<div class="rst-block">%s</div>' %
1064 return literal('<div class="rst-block">%s</div>' %
1060 MarkupRenderer.rst_with_mentions(source))
1065 MarkupRenderer.rst_with_mentions(source))
1061
1066
1062
1067
1063 def changeset_status(repo, revision):
1068 def changeset_status(repo, revision):
1064 return ChangesetStatusModel().get_status(repo, revision)
1069 return ChangesetStatusModel().get_status(repo, revision)
1065
1070
1066
1071
1067 def changeset_status_lbl(changeset_status):
1072 def changeset_status_lbl(changeset_status):
1068 return dict(ChangesetStatus.STATUSES).get(changeset_status)
1073 return dict(ChangesetStatus.STATUSES).get(changeset_status)
1069
1074
1070
1075
1071 def get_permission_name(key):
1076 def get_permission_name(key):
1072 return dict(Permission.PERMS).get(key)
1077 return dict(Permission.PERMS).get(key)
@@ -1,413 +1,416 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.indexers.daemon
3 rhodecode.lib.indexers.daemon
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 A daemon will read from task table and run tasks
6 A daemon will read from task table and run tasks
7
7
8 :created_on: Jan 26, 2010
8 :created_on: Jan 26, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 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 from __future__ import with_statement
25 from __future__ import with_statement
26
26
27 import os
27 import os
28 import sys
28 import sys
29 import logging
29 import logging
30 import traceback
30 import traceback
31
31
32 from shutil import rmtree
32 from shutil import rmtree
33 from time import mktime
33 from time import mktime
34
34
35 from os.path import dirname as dn
35 from os.path import dirname as dn
36 from os.path import join as jn
36 from os.path import join as jn
37
37
38 #to get the rhodecode import
38 #to get the rhodecode import
39 project_path = dn(dn(dn(dn(os.path.realpath(__file__)))))
39 project_path = dn(dn(dn(dn(os.path.realpath(__file__)))))
40 sys.path.append(project_path)
40 sys.path.append(project_path)
41
41
42 from rhodecode.config.conf import INDEX_EXTENSIONS
42 from rhodecode.config.conf import INDEX_EXTENSIONS
43 from rhodecode.model.scm import ScmModel
43 from rhodecode.model.scm import ScmModel
44 from rhodecode.lib.utils2 import safe_unicode
44 from rhodecode.lib.utils2 import safe_unicode
45 from rhodecode.lib.indexers import SCHEMA, IDX_NAME, CHGSETS_SCHEMA, \
45 from rhodecode.lib.indexers import SCHEMA, IDX_NAME, CHGSETS_SCHEMA, \
46 CHGSET_IDX_NAME
46 CHGSET_IDX_NAME
47
47
48 from rhodecode.lib.vcs.exceptions import ChangesetError, RepositoryError, \
48 from rhodecode.lib.vcs.exceptions import ChangesetError, RepositoryError, \
49 NodeDoesNotExistError
49 NodeDoesNotExistError
50
50
51 from whoosh.index import create_in, open_dir, exists_in
51 from whoosh.index import create_in, open_dir, exists_in
52 from whoosh.query import *
52 from whoosh.query import *
53 from whoosh.qparser import QueryParser
53 from whoosh.qparser import QueryParser
54
54
55 log = logging.getLogger('whoosh_indexer')
55 log = logging.getLogger('whoosh_indexer')
56
56
57
57
58 class WhooshIndexingDaemon(object):
58 class WhooshIndexingDaemon(object):
59 """
59 """
60 Daemon for atomic indexing jobs
60 Daemon for atomic indexing jobs
61 """
61 """
62
62
63 def __init__(self, indexname=IDX_NAME, index_location=None,
63 def __init__(self, indexname=IDX_NAME, index_location=None,
64 repo_location=None, sa=None, repo_list=None,
64 repo_location=None, sa=None, repo_list=None,
65 repo_update_list=None):
65 repo_update_list=None):
66 self.indexname = indexname
66 self.indexname = indexname
67
67
68 self.index_location = index_location
68 self.index_location = index_location
69 if not index_location:
69 if not index_location:
70 raise Exception('You have to provide index location')
70 raise Exception('You have to provide index location')
71
71
72 self.repo_location = repo_location
72 self.repo_location = repo_location
73 if not repo_location:
73 if not repo_location:
74 raise Exception('You have to provide repositories location')
74 raise Exception('You have to provide repositories location')
75
75
76 self.repo_paths = ScmModel(sa).repo_scan(self.repo_location)
76 self.repo_paths = ScmModel(sa).repo_scan(self.repo_location)
77
77
78 #filter repo list
78 #filter repo list
79 if repo_list:
79 if repo_list:
80 #Fix non-ascii repo names to unicode
81 repo_list = map(safe_unicode, repo_list)
80 self.filtered_repo_paths = {}
82 self.filtered_repo_paths = {}
81 for repo_name, repo in self.repo_paths.items():
83 for repo_name, repo in self.repo_paths.items():
82 if repo_name in repo_list:
84 if repo_name in repo_list:
83 self.filtered_repo_paths[repo_name] = repo
85 self.filtered_repo_paths[repo_name] = repo
84
86
85 self.repo_paths = self.filtered_repo_paths
87 self.repo_paths = self.filtered_repo_paths
86
88
87 #filter update repo list
89 #filter update repo list
88 self.filtered_repo_update_paths = {}
90 self.filtered_repo_update_paths = {}
89 if repo_update_list:
91 if repo_update_list:
90 self.filtered_repo_update_paths = {}
92 self.filtered_repo_update_paths = {}
91 for repo_name, repo in self.repo_paths.items():
93 for repo_name, repo in self.repo_paths.items():
92 if repo_name in repo_update_list:
94 if repo_name in repo_update_list:
93 self.filtered_repo_update_paths[repo_name] = repo
95 self.filtered_repo_update_paths[repo_name] = repo
94 self.repo_paths = self.filtered_repo_update_paths
96 self.repo_paths = self.filtered_repo_update_paths
95
97
96 self.initial = True
98 self.initial = True
97 if not os.path.isdir(self.index_location):
99 if not os.path.isdir(self.index_location):
98 os.makedirs(self.index_location)
100 os.makedirs(self.index_location)
99 log.info('Cannot run incremental index since it does not'
101 log.info('Cannot run incremental index since it does not'
100 ' yet exist running full build')
102 ' yet exist running full build')
101 elif not exists_in(self.index_location, IDX_NAME):
103 elif not exists_in(self.index_location, IDX_NAME):
102 log.info('Running full index build as the file content'
104 log.info('Running full index build as the file content'
103 ' index does not exist')
105 ' index does not exist')
104 elif not exists_in(self.index_location, CHGSET_IDX_NAME):
106 elif not exists_in(self.index_location, CHGSET_IDX_NAME):
105 log.info('Running full index build as the changeset'
107 log.info('Running full index build as the changeset'
106 ' index does not exist')
108 ' index does not exist')
107 else:
109 else:
108 self.initial = False
110 self.initial = False
109
111
110 def get_paths(self, repo):
112 def get_paths(self, repo):
111 """
113 """
112 recursive walk in root dir and return a set of all path in that dir
114 recursive walk in root dir and return a set of all path in that dir
113 based on repository walk function
115 based on repository walk function
114 """
116 """
115 index_paths_ = set()
117 index_paths_ = set()
116 try:
118 try:
117 tip = repo.get_changeset('tip')
119 tip = repo.get_changeset('tip')
118 for _topnode, _dirs, files in tip.walk('/'):
120 for _topnode, _dirs, files in tip.walk('/'):
119 for f in files:
121 for f in files:
120 index_paths_.add(jn(repo.path, f.path))
122 index_paths_.add(jn(repo.path, f.path))
121
123
122 except RepositoryError:
124 except RepositoryError:
123 log.debug(traceback.format_exc())
125 log.debug(traceback.format_exc())
124 pass
126 pass
125 return index_paths_
127 return index_paths_
126
128
127 def get_node(self, repo, path):
129 def get_node(self, repo, path):
128 n_path = path[len(repo.path) + 1:]
130 n_path = path[len(repo.path) + 1:]
129 node = repo.get_changeset().get_node(n_path)
131 node = repo.get_changeset().get_node(n_path)
130 return node
132 return node
131
133
132 def get_node_mtime(self, node):
134 def get_node_mtime(self, node):
133 return mktime(node.last_changeset.date.timetuple())
135 return mktime(node.last_changeset.date.timetuple())
134
136
135 def add_doc(self, writer, path, repo, repo_name):
137 def add_doc(self, writer, path, repo, repo_name):
136 """
138 """
137 Adding doc to writer this function itself fetches data from
139 Adding doc to writer this function itself fetches data from
138 the instance of vcs backend
140 the instance of vcs backend
139 """
141 """
140
142
141 node = self.get_node(repo, path)
143 node = self.get_node(repo, path)
142 indexed = indexed_w_content = 0
144 indexed = indexed_w_content = 0
143 # we just index the content of chosen files, and skip binary files
145 # we just index the content of chosen files, and skip binary files
144 if node.extension in INDEX_EXTENSIONS and not node.is_binary:
146 if node.extension in INDEX_EXTENSIONS and not node.is_binary:
145 u_content = node.content
147 u_content = node.content
146 if not isinstance(u_content, unicode):
148 if not isinstance(u_content, unicode):
147 log.warning(' >> %s Could not get this content as unicode '
149 log.warning(' >> %s Could not get this content as unicode '
148 'replacing with empty content' % path)
150 'replacing with empty content' % path)
149 u_content = u''
151 u_content = u''
150 else:
152 else:
151 log.debug(' >> %s [WITH CONTENT]' % path)
153 log.debug(' >> %s [WITH CONTENT]' % path)
152 indexed_w_content += 1
154 indexed_w_content += 1
153
155
154 else:
156 else:
155 log.debug(' >> %s' % path)
157 log.debug(' >> %s' % path)
156 # just index file name without it's content
158 # just index file name without it's content
157 u_content = u''
159 u_content = u''
158 indexed += 1
160 indexed += 1
159
161
160 p = safe_unicode(path)
162 p = safe_unicode(path)
161 writer.add_document(
163 writer.add_document(
162 fileid=p,
164 fileid=p,
163 owner=unicode(repo.contact),
165 owner=unicode(repo.contact),
164 repository=safe_unicode(repo_name),
166 repository=safe_unicode(repo_name),
165 path=p,
167 path=p,
166 content=u_content,
168 content=u_content,
167 modtime=self.get_node_mtime(node),
169 modtime=self.get_node_mtime(node),
168 extension=node.extension
170 extension=node.extension
169 )
171 )
170 return indexed, indexed_w_content
172 return indexed, indexed_w_content
171
173
172 def index_changesets(self, writer, repo_name, repo, start_rev=None):
174 def index_changesets(self, writer, repo_name, repo, start_rev=None):
173 """
175 """
174 Add all changeset in the vcs repo starting at start_rev
176 Add all changeset in the vcs repo starting at start_rev
175 to the index writer
177 to the index writer
176
178
177 :param writer: the whoosh index writer to add to
179 :param writer: the whoosh index writer to add to
178 :param repo_name: name of the repository from whence the
180 :param repo_name: name of the repository from whence the
179 changeset originates including the repository group
181 changeset originates including the repository group
180 :param repo: the vcs repository instance to index changesets for,
182 :param repo: the vcs repository instance to index changesets for,
181 the presumption is the repo has changesets to index
183 the presumption is the repo has changesets to index
182 :param start_rev=None: the full sha id to start indexing from
184 :param start_rev=None: the full sha id to start indexing from
183 if start_rev is None then index from the first changeset in
185 if start_rev is None then index from the first changeset in
184 the repo
186 the repo
185 """
187 """
186
188
187 if start_rev is None:
189 if start_rev is None:
188 start_rev = repo[0].raw_id
190 start_rev = repo[0].raw_id
189
191
190 log.debug('indexing changesets in %s starting at rev: %s' %
192 log.debug('indexing changesets in %s starting at rev: %s' %
191 (repo_name, start_rev))
193 (repo_name, start_rev))
192
194
193 indexed = 0
195 indexed = 0
194 for cs in repo.get_changesets(start=start_rev):
196 for cs in repo.get_changesets(start=start_rev):
195 log.debug(' >> %s' % cs)
197 log.debug(' >> %s' % cs)
196 writer.add_document(
198 writer.add_document(
197 raw_id=unicode(cs.raw_id),
199 raw_id=unicode(cs.raw_id),
198 owner=unicode(repo.contact),
200 owner=unicode(repo.contact),
199 date=cs._timestamp,
201 date=cs._timestamp,
200 repository=safe_unicode(repo_name),
202 repository=safe_unicode(repo_name),
201 author=cs.author,
203 author=cs.author,
202 message=cs.message,
204 message=cs.message,
203 last=cs.last,
205 last=cs.last,
204 added=u' '.join([safe_unicode(node.path) for node in cs.added]).lower(),
206 added=u' '.join([safe_unicode(node.path) for node in cs.added]).lower(),
205 removed=u' '.join([safe_unicode(node.path) for node in cs.removed]).lower(),
207 removed=u' '.join([safe_unicode(node.path) for node in cs.removed]).lower(),
206 changed=u' '.join([safe_unicode(node.path) for node in cs.changed]).lower(),
208 changed=u' '.join([safe_unicode(node.path) for node in cs.changed]).lower(),
207 parents=u' '.join([cs.raw_id for cs in cs.parents]),
209 parents=u' '.join([cs.raw_id for cs in cs.parents]),
208 )
210 )
209 indexed += 1
211 indexed += 1
210
212
211 log.debug('indexed %d changesets for repo %s' % (indexed, repo_name))
213 log.debug('indexed %d changesets for repo %s' % (indexed, repo_name))
212 return indexed
214 return indexed
213
215
214 def index_files(self, file_idx_writer, repo_name, repo):
216 def index_files(self, file_idx_writer, repo_name, repo):
215 """
217 """
216 Index files for given repo_name
218 Index files for given repo_name
217
219
218 :param file_idx_writer: the whoosh index writer to add to
220 :param file_idx_writer: the whoosh index writer to add to
219 :param repo_name: name of the repository we're indexing
221 :param repo_name: name of the repository we're indexing
220 :param repo: instance of vcs repo
222 :param repo: instance of vcs repo
221 """
223 """
222 i_cnt = iwc_cnt = 0
224 i_cnt = iwc_cnt = 0
223 log.debug('building index for [%s]' % repo.path)
225 log.debug('building index for [%s]' % repo.path)
224 for idx_path in self.get_paths(repo):
226 for idx_path in self.get_paths(repo):
225 i, iwc = self.add_doc(file_idx_writer, idx_path, repo, repo_name)
227 i, iwc = self.add_doc(file_idx_writer, idx_path, repo, repo_name)
226 i_cnt += i
228 i_cnt += i
227 iwc_cnt += iwc
229 iwc_cnt += iwc
228
230
229 log.debug('added %s files %s with content for repo %s' %
231 log.debug('added %s files %s with content for repo %s' %
230 (i_cnt + iwc_cnt, iwc_cnt, repo.path))
232 (i_cnt + iwc_cnt, iwc_cnt, repo.path))
231 return i_cnt, iwc_cnt
233 return i_cnt, iwc_cnt
232
234
233 def update_changeset_index(self):
235 def update_changeset_index(self):
234 idx = open_dir(self.index_location, indexname=CHGSET_IDX_NAME)
236 idx = open_dir(self.index_location, indexname=CHGSET_IDX_NAME)
235
237
236 with idx.searcher() as searcher:
238 with idx.searcher() as searcher:
237 writer = idx.writer()
239 writer = idx.writer()
238 writer_is_dirty = False
240 writer_is_dirty = False
239 try:
241 try:
240 indexed_total = 0
242 indexed_total = 0
243 repo_name = None
241 for repo_name, repo in self.repo_paths.items():
244 for repo_name, repo in self.repo_paths.items():
242 # skip indexing if there aren't any revs in the repo
245 # skip indexing if there aren't any revs in the repo
243 num_of_revs = len(repo)
246 num_of_revs = len(repo)
244 if num_of_revs < 1:
247 if num_of_revs < 1:
245 continue
248 continue
246
249
247 qp = QueryParser('repository', schema=CHGSETS_SCHEMA)
250 qp = QueryParser('repository', schema=CHGSETS_SCHEMA)
248 q = qp.parse(u"last:t AND %s" % repo_name)
251 q = qp.parse(u"last:t AND %s" % repo_name)
249
252
250 results = searcher.search(q)
253 results = searcher.search(q)
251
254
252 # default to scanning the entire repo
255 # default to scanning the entire repo
253 last_rev = 0
256 last_rev = 0
254 start_id = None
257 start_id = None
255
258
256 if len(results) > 0:
259 if len(results) > 0:
257 # assuming that there is only one result, if not this
260 # assuming that there is only one result, if not this
258 # may require a full re-index.
261 # may require a full re-index.
259 start_id = results[0]['raw_id']
262 start_id = results[0]['raw_id']
260 last_rev = repo.get_changeset(revision=start_id).revision
263 last_rev = repo.get_changeset(revision=start_id).revision
261
264
262 # there are new changesets to index or a new repo to index
265 # there are new changesets to index or a new repo to index
263 if last_rev == 0 or num_of_revs > last_rev + 1:
266 if last_rev == 0 or num_of_revs > last_rev + 1:
264 # delete the docs in the index for the previous
267 # delete the docs in the index for the previous
265 # last changeset(s)
268 # last changeset(s)
266 for hit in results:
269 for hit in results:
267 q = qp.parse(u"last:t AND %s AND raw_id:%s" %
270 q = qp.parse(u"last:t AND %s AND raw_id:%s" %
268 (repo_name, hit['raw_id']))
271 (repo_name, hit['raw_id']))
269 writer.delete_by_query(q)
272 writer.delete_by_query(q)
270
273
271 # index from the previous last changeset + all new ones
274 # index from the previous last changeset + all new ones
272 indexed_total += self.index_changesets(writer,
275 indexed_total += self.index_changesets(writer,
273 repo_name, repo, start_id)
276 repo_name, repo, start_id)
274 writer_is_dirty = True
277 writer_is_dirty = True
275 log.debug('indexed %s changesets for repo %s' % (
278 log.debug('indexed %s changesets for repo %s' % (
276 indexed_total, repo_name)
279 indexed_total, repo_name)
277 )
280 )
278 finally:
281 finally:
279 if writer_is_dirty:
282 if writer_is_dirty:
280 log.debug('>> COMMITING CHANGES TO CHANGESET INDEX<<')
283 log.debug('>> COMMITING CHANGES TO CHANGESET INDEX<<')
281 writer.commit(merge=True)
284 writer.commit(merge=True)
282 log.debug('>> COMMITTED CHANGES TO CHANGESET INDEX<<')
285 log.debug('>>> FINISHED REBUILDING CHANGESET INDEX <<<')
283 else:
286 else:
284 writer.cancel
287 writer.cancel
285 log.debug('>> NOTHING TO COMMIT<<')
288 log.debug('>> NOTHING TO COMMIT TO CHANGESET INDEX<<')
286
289
287 def update_file_index(self):
290 def update_file_index(self):
288 log.debug((u'STARTING INCREMENTAL INDEXING UPDATE FOR EXTENSIONS %s '
291 log.debug((u'STARTING INCREMENTAL INDEXING UPDATE FOR EXTENSIONS %s '
289 'AND REPOS %s') % (INDEX_EXTENSIONS, self.repo_paths.keys()))
292 'AND REPOS %s') % (INDEX_EXTENSIONS, self.repo_paths.keys()))
290
293
291 idx = open_dir(self.index_location, indexname=self.indexname)
294 idx = open_dir(self.index_location, indexname=self.indexname)
292 # The set of all paths in the index
295 # The set of all paths in the index
293 indexed_paths = set()
296 indexed_paths = set()
294 # The set of all paths we need to re-index
297 # The set of all paths we need to re-index
295 to_index = set()
298 to_index = set()
296
299
297 writer = idx.writer()
300 writer = idx.writer()
298 writer_is_dirty = False
301 writer_is_dirty = False
299 try:
302 try:
300 with idx.reader() as reader:
303 with idx.reader() as reader:
301
304
302 # Loop over the stored fields in the index
305 # Loop over the stored fields in the index
303 for fields in reader.all_stored_fields():
306 for fields in reader.all_stored_fields():
304 indexed_path = fields['path']
307 indexed_path = fields['path']
305 indexed_repo_path = fields['repository']
308 indexed_repo_path = fields['repository']
306 indexed_paths.add(indexed_path)
309 indexed_paths.add(indexed_path)
307
310
308 if not indexed_repo_path in self.filtered_repo_update_paths:
311 if not indexed_repo_path in self.filtered_repo_update_paths:
309 continue
312 continue
310
313
311 repo = self.repo_paths[indexed_repo_path]
314 repo = self.repo_paths[indexed_repo_path]
312
315
313 try:
316 try:
314 node = self.get_node(repo, indexed_path)
317 node = self.get_node(repo, indexed_path)
315 # Check if this file was changed since it was indexed
318 # Check if this file was changed since it was indexed
316 indexed_time = fields['modtime']
319 indexed_time = fields['modtime']
317 mtime = self.get_node_mtime(node)
320 mtime = self.get_node_mtime(node)
318 if mtime > indexed_time:
321 if mtime > indexed_time:
319 # The file has changed, delete it and add it to
322 # The file has changed, delete it and add it to
320 # the list of files to reindex
323 # the list of files to reindex
321 log.debug(
324 log.debug(
322 'adding to reindex list %s mtime: %s vs %s' % (
325 'adding to reindex list %s mtime: %s vs %s' % (
323 indexed_path, mtime, indexed_time)
326 indexed_path, mtime, indexed_time)
324 )
327 )
325 writer.delete_by_term('fileid', indexed_path)
328 writer.delete_by_term('fileid', indexed_path)
326 writer_is_dirty = True
329 writer_is_dirty = True
327
330
328 to_index.add(indexed_path)
331 to_index.add(indexed_path)
329 except (ChangesetError, NodeDoesNotExistError):
332 except (ChangesetError, NodeDoesNotExistError):
330 # This file was deleted since it was indexed
333 # This file was deleted since it was indexed
331 log.debug('removing from index %s' % indexed_path)
334 log.debug('removing from index %s' % indexed_path)
332 writer.delete_by_term('path', indexed_path)
335 writer.delete_by_term('path', indexed_path)
333 writer_is_dirty = True
336 writer_is_dirty = True
334
337
335 # Loop over the files in the filesystem
338 # Loop over the files in the filesystem
336 # Assume we have a function that gathers the filenames of the
339 # Assume we have a function that gathers the filenames of the
337 # documents to be indexed
340 # documents to be indexed
338 ri_cnt_total = 0 # indexed
341 ri_cnt_total = 0 # indexed
339 riwc_cnt_total = 0 # indexed with content
342 riwc_cnt_total = 0 # indexed with content
340 for repo_name, repo in self.repo_paths.items():
343 for repo_name, repo in self.repo_paths.items():
341 # skip indexing if there aren't any revisions
344 # skip indexing if there aren't any revisions
342 if len(repo) < 1:
345 if len(repo) < 1:
343 continue
346 continue
344 ri_cnt = 0 # indexed
347 ri_cnt = 0 # indexed
345 riwc_cnt = 0 # indexed with content
348 riwc_cnt = 0 # indexed with content
346 for path in self.get_paths(repo):
349 for path in self.get_paths(repo):
347 path = safe_unicode(path)
350 path = safe_unicode(path)
348 if path in to_index or path not in indexed_paths:
351 if path in to_index or path not in indexed_paths:
349
352
350 # This is either a file that's changed, or a new file
353 # This is either a file that's changed, or a new file
351 # that wasn't indexed before. So index it!
354 # that wasn't indexed before. So index it!
352 i, iwc = self.add_doc(writer, path, repo, repo_name)
355 i, iwc = self.add_doc(writer, path, repo, repo_name)
353 writer_is_dirty = True
356 writer_is_dirty = True
354 log.debug('re indexing %s' % path)
357 log.debug('re indexing %s' % path)
355 ri_cnt += i
358 ri_cnt += i
356 ri_cnt_total += 1
359 ri_cnt_total += 1
357 riwc_cnt += iwc
360 riwc_cnt += iwc
358 riwc_cnt_total += iwc
361 riwc_cnt_total += iwc
359 log.debug('added %s files %s with content for repo %s' % (
362 log.debug('added %s files %s with content for repo %s' % (
360 ri_cnt + riwc_cnt, riwc_cnt, repo.path)
363 ri_cnt + riwc_cnt, riwc_cnt, repo.path)
361 )
364 )
362 log.debug('indexed %s files in total and %s with content' % (
365 log.debug('indexed %s files in total and %s with content' % (
363 ri_cnt_total, riwc_cnt_total)
366 ri_cnt_total, riwc_cnt_total)
364 )
367 )
365 finally:
368 finally:
366 if writer_is_dirty:
369 if writer_is_dirty:
367 log.debug('>> COMMITING CHANGES <<')
370 log.debug('>> COMMITING CHANGES TO FILE INDEX <<')
368 writer.commit(merge=True)
371 writer.commit(merge=True)
369 log.debug('>>> FINISHED REBUILDING INDEX <<<')
372 log.debug('>>> FINISHED REBUILDING FILE INDEX <<<')
370 else:
373 else:
371 log.debug('>> NOTHING TO COMMIT<<')
374 log.debug('>> NOTHING TO COMMIT TO FILE INDEX <<')
372 writer.cancel()
375 writer.cancel()
373
376
374 def build_indexes(self):
377 def build_indexes(self):
375 if os.path.exists(self.index_location):
378 if os.path.exists(self.index_location):
376 log.debug('removing previous index')
379 log.debug('removing previous index')
377 rmtree(self.index_location)
380 rmtree(self.index_location)
378
381
379 if not os.path.exists(self.index_location):
382 if not os.path.exists(self.index_location):
380 os.mkdir(self.index_location)
383 os.mkdir(self.index_location)
381
384
382 chgset_idx = create_in(self.index_location, CHGSETS_SCHEMA,
385 chgset_idx = create_in(self.index_location, CHGSETS_SCHEMA,
383 indexname=CHGSET_IDX_NAME)
386 indexname=CHGSET_IDX_NAME)
384 chgset_idx_writer = chgset_idx.writer()
387 chgset_idx_writer = chgset_idx.writer()
385
388
386 file_idx = create_in(self.index_location, SCHEMA, indexname=IDX_NAME)
389 file_idx = create_in(self.index_location, SCHEMA, indexname=IDX_NAME)
387 file_idx_writer = file_idx.writer()
390 file_idx_writer = file_idx.writer()
388 log.debug('BUILDING INDEX FOR EXTENSIONS %s '
391 log.debug('BUILDING INDEX FOR EXTENSIONS %s '
389 'AND REPOS %s' % (INDEX_EXTENSIONS, self.repo_paths.keys()))
392 'AND REPOS %s' % (INDEX_EXTENSIONS, self.repo_paths.keys()))
390
393
391 for repo_name, repo in self.repo_paths.items():
394 for repo_name, repo in self.repo_paths.items():
392 # skip indexing if there aren't any revisions
395 # skip indexing if there aren't any revisions
393 if len(repo) < 1:
396 if len(repo) < 1:
394 continue
397 continue
395
398
396 self.index_files(file_idx_writer, repo_name, repo)
399 self.index_files(file_idx_writer, repo_name, repo)
397 self.index_changesets(chgset_idx_writer, repo_name, repo)
400 self.index_changesets(chgset_idx_writer, repo_name, repo)
398
401
399 log.debug('>> COMMITING CHANGES <<')
402 log.debug('>> COMMITING CHANGES <<')
400 file_idx_writer.commit(merge=True)
403 file_idx_writer.commit(merge=True)
401 chgset_idx_writer.commit(merge=True)
404 chgset_idx_writer.commit(merge=True)
402 log.debug('>>> FINISHED BUILDING INDEX <<<')
405 log.debug('>>> FINISHED BUILDING INDEX <<<')
403
406
404 def update_indexes(self):
407 def update_indexes(self):
405 self.update_file_index()
408 self.update_file_index()
406 self.update_changeset_index()
409 self.update_changeset_index()
407
410
408 def run(self, full_index=False):
411 def run(self, full_index=False):
409 """Run daemon"""
412 """Run daemon"""
410 if full_index or self.initial:
413 if full_index or self.initial:
411 self.build_indexes()
414 self.build_indexes()
412 else:
415 else:
413 self.update_indexes()
416 self.update_indexes()
@@ -1,466 +1,483 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.utils
3 rhodecode.lib.utils
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) 2011-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2011-2012 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 re
26 import re
27 import time
27 import time
28 import datetime
28 import datetime
29 from pylons.i18n.translation import _, ungettext
29 from pylons.i18n.translation import _, ungettext
30 from rhodecode.lib.vcs.utils.lazy import LazyProperty
30 from rhodecode.lib.vcs.utils.lazy import LazyProperty
31
31
32
32
33 def __get_lem():
33 def __get_lem():
34 """
34 """
35 Get language extension map based on what's inside pygments lexers
35 Get language extension map based on what's inside pygments lexers
36 """
36 """
37 from pygments import lexers
37 from pygments import lexers
38 from string import lower
38 from string import lower
39 from collections import defaultdict
39 from collections import defaultdict
40
40
41 d = defaultdict(lambda: [])
41 d = defaultdict(lambda: [])
42
42
43 def __clean(s):
43 def __clean(s):
44 s = s.lstrip('*')
44 s = s.lstrip('*')
45 s = s.lstrip('.')
45 s = s.lstrip('.')
46
46
47 if s.find('[') != -1:
47 if s.find('[') != -1:
48 exts = []
48 exts = []
49 start, stop = s.find('['), s.find(']')
49 start, stop = s.find('['), s.find(']')
50
50
51 for suffix in s[start + 1:stop]:
51 for suffix in s[start + 1:stop]:
52 exts.append(s[:s.find('[')] + suffix)
52 exts.append(s[:s.find('[')] + suffix)
53 return map(lower, exts)
53 return map(lower, exts)
54 else:
54 else:
55 return map(lower, [s])
55 return map(lower, [s])
56
56
57 for lx, t in sorted(lexers.LEXERS.items()):
57 for lx, t in sorted(lexers.LEXERS.items()):
58 m = map(__clean, t[-2])
58 m = map(__clean, t[-2])
59 if m:
59 if m:
60 m = reduce(lambda x, y: x + y, m)
60 m = reduce(lambda x, y: x + y, m)
61 for ext in m:
61 for ext in m:
62 desc = lx.replace('Lexer', '')
62 desc = lx.replace('Lexer', '')
63 d[ext].append(desc)
63 d[ext].append(desc)
64
64
65 return dict(d)
65 return dict(d)
66
66
67 def str2bool(_str):
67 def str2bool(_str):
68 """
68 """
69 returs True/False value from given string, it tries to translate the
69 returs True/False value from given string, it tries to translate the
70 string into boolean
70 string into boolean
71
71
72 :param _str: string value to translate into boolean
72 :param _str: string value to translate into boolean
73 :rtype: boolean
73 :rtype: boolean
74 :returns: boolean from given string
74 :returns: boolean from given string
75 """
75 """
76 if _str is None:
76 if _str is None:
77 return False
77 return False
78 if _str in (True, False):
78 if _str in (True, False):
79 return _str
79 return _str
80 _str = str(_str).strip().lower()
80 _str = str(_str).strip().lower()
81 return _str in ('t', 'true', 'y', 'yes', 'on', '1')
81 return _str in ('t', 'true', 'y', 'yes', 'on', '1')
82
82
83
83
84 def convert_line_endings(line, mode):
84 def convert_line_endings(line, mode):
85 """
85 """
86 Converts a given line "line end" accordingly to given mode
86 Converts a given line "line end" accordingly to given mode
87
87
88 Available modes are::
88 Available modes are::
89 0 - Unix
89 0 - Unix
90 1 - Mac
90 1 - Mac
91 2 - DOS
91 2 - DOS
92
92
93 :param line: given line to convert
93 :param line: given line to convert
94 :param mode: mode to convert to
94 :param mode: mode to convert to
95 :rtype: str
95 :rtype: str
96 :return: converted line according to mode
96 :return: converted line according to mode
97 """
97 """
98 from string import replace
98 from string import replace
99
99
100 if mode == 0:
100 if mode == 0:
101 line = replace(line, '\r\n', '\n')
101 line = replace(line, '\r\n', '\n')
102 line = replace(line, '\r', '\n')
102 line = replace(line, '\r', '\n')
103 elif mode == 1:
103 elif mode == 1:
104 line = replace(line, '\r\n', '\r')
104 line = replace(line, '\r\n', '\r')
105 line = replace(line, '\n', '\r')
105 line = replace(line, '\n', '\r')
106 elif mode == 2:
106 elif mode == 2:
107 line = re.sub("\r(?!\n)|(?<!\r)\n", "\r\n", line)
107 line = re.sub("\r(?!\n)|(?<!\r)\n", "\r\n", line)
108 return line
108 return line
109
109
110
110
111 def detect_mode(line, default):
111 def detect_mode(line, default):
112 """
112 """
113 Detects line break for given line, if line break couldn't be found
113 Detects line break for given line, if line break couldn't be found
114 given default value is returned
114 given default value is returned
115
115
116 :param line: str line
116 :param line: str line
117 :param default: default
117 :param default: default
118 :rtype: int
118 :rtype: int
119 :return: value of line end on of 0 - Unix, 1 - Mac, 2 - DOS
119 :return: value of line end on of 0 - Unix, 1 - Mac, 2 - DOS
120 """
120 """
121 if line.endswith('\r\n'):
121 if line.endswith('\r\n'):
122 return 2
122 return 2
123 elif line.endswith('\n'):
123 elif line.endswith('\n'):
124 return 0
124 return 0
125 elif line.endswith('\r'):
125 elif line.endswith('\r'):
126 return 1
126 return 1
127 else:
127 else:
128 return default
128 return default
129
129
130
130
131 def generate_api_key(username, salt=None):
131 def generate_api_key(username, salt=None):
132 """
132 """
133 Generates unique API key for given username, if salt is not given
133 Generates unique API key for given username, if salt is not given
134 it'll be generated from some random string
134 it'll be generated from some random string
135
135
136 :param username: username as string
136 :param username: username as string
137 :param salt: salt to hash generate KEY
137 :param salt: salt to hash generate KEY
138 :rtype: str
138 :rtype: str
139 :returns: sha1 hash from username+salt
139 :returns: sha1 hash from username+salt
140 """
140 """
141 from tempfile import _RandomNameSequence
141 from tempfile import _RandomNameSequence
142 import hashlib
142 import hashlib
143
143
144 if salt is None:
144 if salt is None:
145 salt = _RandomNameSequence().next()
145 salt = _RandomNameSequence().next()
146
146
147 return hashlib.sha1(username + salt).hexdigest()
147 return hashlib.sha1(username + salt).hexdigest()
148
148
149
149
150 def safe_int(val, default=None):
151 """
152 Returns int() of val if val is not convertable to int use default
153 instead
154
155 :param val:
156 :param default:
157 """
158
159 try:
160 val = int(val)
161 except ValueError:
162 val = default
163
164 return val
165
166
150 def safe_unicode(str_, from_encoding=None):
167 def safe_unicode(str_, from_encoding=None):
151 """
168 """
152 safe unicode function. Does few trick to turn str_ into unicode
169 safe unicode function. Does few trick to turn str_ into unicode
153
170
154 In case of UnicodeDecode error we try to return it with encoding detected
171 In case of UnicodeDecode error we try to return it with encoding detected
155 by chardet library if it fails fallback to unicode with errors replaced
172 by chardet library if it fails fallback to unicode with errors replaced
156
173
157 :param str_: string to decode
174 :param str_: string to decode
158 :rtype: unicode
175 :rtype: unicode
159 :returns: unicode object
176 :returns: unicode object
160 """
177 """
161 if isinstance(str_, unicode):
178 if isinstance(str_, unicode):
162 return str_
179 return str_
163
180
164 if not from_encoding:
181 if not from_encoding:
165 import rhodecode
182 import rhodecode
166 DEFAULT_ENCODING = rhodecode.CONFIG.get('default_encoding','utf8')
183 DEFAULT_ENCODING = rhodecode.CONFIG.get('default_encoding','utf8')
167 from_encoding = DEFAULT_ENCODING
184 from_encoding = DEFAULT_ENCODING
168
185
169 try:
186 try:
170 return unicode(str_)
187 return unicode(str_)
171 except UnicodeDecodeError:
188 except UnicodeDecodeError:
172 pass
189 pass
173
190
174 try:
191 try:
175 return unicode(str_, from_encoding)
192 return unicode(str_, from_encoding)
176 except UnicodeDecodeError:
193 except UnicodeDecodeError:
177 pass
194 pass
178
195
179 try:
196 try:
180 import chardet
197 import chardet
181 encoding = chardet.detect(str_)['encoding']
198 encoding = chardet.detect(str_)['encoding']
182 if encoding is None:
199 if encoding is None:
183 raise Exception()
200 raise Exception()
184 return str_.decode(encoding)
201 return str_.decode(encoding)
185 except (ImportError, UnicodeDecodeError, Exception):
202 except (ImportError, UnicodeDecodeError, Exception):
186 return unicode(str_, from_encoding, 'replace')
203 return unicode(str_, from_encoding, 'replace')
187
204
188
205
189 def safe_str(unicode_, to_encoding=None):
206 def safe_str(unicode_, to_encoding=None):
190 """
207 """
191 safe str function. Does few trick to turn unicode_ into string
208 safe str function. Does few trick to turn unicode_ into string
192
209
193 In case of UnicodeEncodeError we try to return it with encoding detected
210 In case of UnicodeEncodeError we try to return it with encoding detected
194 by chardet library if it fails fallback to string with errors replaced
211 by chardet library if it fails fallback to string with errors replaced
195
212
196 :param unicode_: unicode to encode
213 :param unicode_: unicode to encode
197 :rtype: str
214 :rtype: str
198 :returns: str object
215 :returns: str object
199 """
216 """
200
217
201 # if it's not basestr cast to str
218 # if it's not basestr cast to str
202 if not isinstance(unicode_, basestring):
219 if not isinstance(unicode_, basestring):
203 return str(unicode_)
220 return str(unicode_)
204
221
205 if isinstance(unicode_, str):
222 if isinstance(unicode_, str):
206 return unicode_
223 return unicode_
207
224
208 if not to_encoding:
225 if not to_encoding:
209 import rhodecode
226 import rhodecode
210 DEFAULT_ENCODING = rhodecode.CONFIG.get('default_encoding','utf8')
227 DEFAULT_ENCODING = rhodecode.CONFIG.get('default_encoding','utf8')
211 to_encoding = DEFAULT_ENCODING
228 to_encoding = DEFAULT_ENCODING
212
229
213 try:
230 try:
214 return unicode_.encode(to_encoding)
231 return unicode_.encode(to_encoding)
215 except UnicodeEncodeError:
232 except UnicodeEncodeError:
216 pass
233 pass
217
234
218 try:
235 try:
219 import chardet
236 import chardet
220 encoding = chardet.detect(unicode_)['encoding']
237 encoding = chardet.detect(unicode_)['encoding']
221 if encoding is None:
238 if encoding is None:
222 raise UnicodeEncodeError()
239 raise UnicodeEncodeError()
223
240
224 return unicode_.encode(encoding)
241 return unicode_.encode(encoding)
225 except (ImportError, UnicodeEncodeError):
242 except (ImportError, UnicodeEncodeError):
226 return unicode_.encode(to_encoding, 'replace')
243 return unicode_.encode(to_encoding, 'replace')
227
244
228 return safe_str
245 return safe_str
229
246
230
247
231 def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs):
248 def engine_from_config(configuration, prefix='sqlalchemy.', **kwargs):
232 """
249 """
233 Custom engine_from_config functions that makes sure we use NullPool for
250 Custom engine_from_config functions that makes sure we use NullPool for
234 file based sqlite databases. This prevents errors on sqlite. This only
251 file based sqlite databases. This prevents errors on sqlite. This only
235 applies to sqlalchemy versions < 0.7.0
252 applies to sqlalchemy versions < 0.7.0
236
253
237 """
254 """
238 import sqlalchemy
255 import sqlalchemy
239 from sqlalchemy import engine_from_config as efc
256 from sqlalchemy import engine_from_config as efc
240 import logging
257 import logging
241
258
242 if int(sqlalchemy.__version__.split('.')[1]) < 7:
259 if int(sqlalchemy.__version__.split('.')[1]) < 7:
243
260
244 # This solution should work for sqlalchemy < 0.7.0, and should use
261 # This solution should work for sqlalchemy < 0.7.0, and should use
245 # proxy=TimerProxy() for execution time profiling
262 # proxy=TimerProxy() for execution time profiling
246
263
247 from sqlalchemy.pool import NullPool
264 from sqlalchemy.pool import NullPool
248 url = configuration[prefix + 'url']
265 url = configuration[prefix + 'url']
249
266
250 if url.startswith('sqlite'):
267 if url.startswith('sqlite'):
251 kwargs.update({'poolclass': NullPool})
268 kwargs.update({'poolclass': NullPool})
252 return efc(configuration, prefix, **kwargs)
269 return efc(configuration, prefix, **kwargs)
253 else:
270 else:
254 import time
271 import time
255 from sqlalchemy import event
272 from sqlalchemy import event
256 from sqlalchemy.engine import Engine
273 from sqlalchemy.engine import Engine
257
274
258 log = logging.getLogger('sqlalchemy.engine')
275 log = logging.getLogger('sqlalchemy.engine')
259 BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = xrange(30, 38)
276 BLACK, RED, GREEN, YELLOW, BLUE, MAGENTA, CYAN, WHITE = xrange(30, 38)
260 engine = efc(configuration, prefix, **kwargs)
277 engine = efc(configuration, prefix, **kwargs)
261
278
262 def color_sql(sql):
279 def color_sql(sql):
263 COLOR_SEQ = "\033[1;%dm"
280 COLOR_SEQ = "\033[1;%dm"
264 COLOR_SQL = YELLOW
281 COLOR_SQL = YELLOW
265 normal = '\x1b[0m'
282 normal = '\x1b[0m'
266 return ''.join([COLOR_SEQ % COLOR_SQL, sql, normal])
283 return ''.join([COLOR_SEQ % COLOR_SQL, sql, normal])
267
284
268 if configuration['debug']:
285 if configuration['debug']:
269 #attach events only for debug configuration
286 #attach events only for debug configuration
270
287
271 def before_cursor_execute(conn, cursor, statement,
288 def before_cursor_execute(conn, cursor, statement,
272 parameters, context, executemany):
289 parameters, context, executemany):
273 context._query_start_time = time.time()
290 context._query_start_time = time.time()
274 log.info(color_sql(">>>>> STARTING QUERY >>>>>"))
291 log.info(color_sql(">>>>> STARTING QUERY >>>>>"))
275
292
276 def after_cursor_execute(conn, cursor, statement,
293 def after_cursor_execute(conn, cursor, statement,
277 parameters, context, executemany):
294 parameters, context, executemany):
278 total = time.time() - context._query_start_time
295 total = time.time() - context._query_start_time
279 log.info(color_sql("<<<<< TOTAL TIME: %f <<<<<" % total))
296 log.info(color_sql("<<<<< TOTAL TIME: %f <<<<<" % total))
280
297
281 event.listen(engine, "before_cursor_execute",
298 event.listen(engine, "before_cursor_execute",
282 before_cursor_execute)
299 before_cursor_execute)
283 event.listen(engine, "after_cursor_execute",
300 event.listen(engine, "after_cursor_execute",
284 after_cursor_execute)
301 after_cursor_execute)
285
302
286 return engine
303 return engine
287
304
288
305
289 def age(prevdate):
306 def age(prevdate):
290 """
307 """
291 turns a datetime into an age string.
308 turns a datetime into an age string.
292
309
293 :param prevdate: datetime object
310 :param prevdate: datetime object
294 :rtype: unicode
311 :rtype: unicode
295 :returns: unicode words describing age
312 :returns: unicode words describing age
296 """
313 """
297
314
298 order = ['year', 'month', 'day', 'hour', 'minute', 'second']
315 order = ['year', 'month', 'day', 'hour', 'minute', 'second']
299 deltas = {}
316 deltas = {}
300
317
301 # Get date parts deltas
318 # Get date parts deltas
302 now = datetime.datetime.now()
319 now = datetime.datetime.now()
303 for part in order:
320 for part in order:
304 deltas[part] = getattr(now, part) - getattr(prevdate, part)
321 deltas[part] = getattr(now, part) - getattr(prevdate, part)
305
322
306 # Fix negative offsets (there is 1 second between 10:59:59 and 11:00:00,
323 # Fix negative offsets (there is 1 second between 10:59:59 and 11:00:00,
307 # not 1 hour, -59 minutes and -59 seconds)
324 # not 1 hour, -59 minutes and -59 seconds)
308
325
309 for num, length in [(5, 60), (4, 60), (3, 24)]: # seconds, minutes, hours
326 for num, length in [(5, 60), (4, 60), (3, 24)]: # seconds, minutes, hours
310 part = order[num]
327 part = order[num]
311 carry_part = order[num - 1]
328 carry_part = order[num - 1]
312
329
313 if deltas[part] < 0:
330 if deltas[part] < 0:
314 deltas[part] += length
331 deltas[part] += length
315 deltas[carry_part] -= 1
332 deltas[carry_part] -= 1
316
333
317 # Same thing for days except that the increment depends on the (variable)
334 # Same thing for days except that the increment depends on the (variable)
318 # number of days in the month
335 # number of days in the month
319 month_lengths = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
336 month_lengths = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
320 if deltas['day'] < 0:
337 if deltas['day'] < 0:
321 if prevdate.month == 2 and (prevdate.year % 4 == 0 and
338 if prevdate.month == 2 and (prevdate.year % 4 == 0 and
322 (prevdate.year % 100 != 0 or prevdate.year % 400 == 0)):
339 (prevdate.year % 100 != 0 or prevdate.year % 400 == 0)):
323 deltas['day'] += 29
340 deltas['day'] += 29
324 else:
341 else:
325 deltas['day'] += month_lengths[prevdate.month - 1]
342 deltas['day'] += month_lengths[prevdate.month - 1]
326
343
327 deltas['month'] -= 1
344 deltas['month'] -= 1
328
345
329 if deltas['month'] < 0:
346 if deltas['month'] < 0:
330 deltas['month'] += 12
347 deltas['month'] += 12
331 deltas['year'] -= 1
348 deltas['year'] -= 1
332
349
333 # Format the result
350 # Format the result
334 fmt_funcs = {
351 fmt_funcs = {
335 'year': lambda d: ungettext(u'%d year', '%d years', d) % d,
352 'year': lambda d: ungettext(u'%d year', '%d years', d) % d,
336 'month': lambda d: ungettext(u'%d month', '%d months', d) % d,
353 'month': lambda d: ungettext(u'%d month', '%d months', d) % d,
337 'day': lambda d: ungettext(u'%d day', '%d days', d) % d,
354 'day': lambda d: ungettext(u'%d day', '%d days', d) % d,
338 'hour': lambda d: ungettext(u'%d hour', '%d hours', d) % d,
355 'hour': lambda d: ungettext(u'%d hour', '%d hours', d) % d,
339 'minute': lambda d: ungettext(u'%d minute', '%d minutes', d) % d,
356 'minute': lambda d: ungettext(u'%d minute', '%d minutes', d) % d,
340 'second': lambda d: ungettext(u'%d second', '%d seconds', d) % d,
357 'second': lambda d: ungettext(u'%d second', '%d seconds', d) % d,
341 }
358 }
342
359
343 for i, part in enumerate(order):
360 for i, part in enumerate(order):
344 value = deltas[part]
361 value = deltas[part]
345 if value == 0:
362 if value == 0:
346 continue
363 continue
347
364
348 if i < 5:
365 if i < 5:
349 sub_part = order[i + 1]
366 sub_part = order[i + 1]
350 sub_value = deltas[sub_part]
367 sub_value = deltas[sub_part]
351 else:
368 else:
352 sub_value = 0
369 sub_value = 0
353
370
354 if sub_value == 0:
371 if sub_value == 0:
355 return _(u'%s ago') % fmt_funcs[part](value)
372 return _(u'%s ago') % fmt_funcs[part](value)
356
373
357 return _(u'%s and %s ago') % (fmt_funcs[part](value),
374 return _(u'%s and %s ago') % (fmt_funcs[part](value),
358 fmt_funcs[sub_part](sub_value))
375 fmt_funcs[sub_part](sub_value))
359
376
360 return _(u'just now')
377 return _(u'just now')
361
378
362
379
363 def uri_filter(uri):
380 def uri_filter(uri):
364 """
381 """
365 Removes user:password from given url string
382 Removes user:password from given url string
366
383
367 :param uri:
384 :param uri:
368 :rtype: unicode
385 :rtype: unicode
369 :returns: filtered list of strings
386 :returns: filtered list of strings
370 """
387 """
371 if not uri:
388 if not uri:
372 return ''
389 return ''
373
390
374 proto = ''
391 proto = ''
375
392
376 for pat in ('https://', 'http://'):
393 for pat in ('https://', 'http://'):
377 if uri.startswith(pat):
394 if uri.startswith(pat):
378 uri = uri[len(pat):]
395 uri = uri[len(pat):]
379 proto = pat
396 proto = pat
380 break
397 break
381
398
382 # remove passwords and username
399 # remove passwords and username
383 uri = uri[uri.find('@') + 1:]
400 uri = uri[uri.find('@') + 1:]
384
401
385 # get the port
402 # get the port
386 cred_pos = uri.find(':')
403 cred_pos = uri.find(':')
387 if cred_pos == -1:
404 if cred_pos == -1:
388 host, port = uri, None
405 host, port = uri, None
389 else:
406 else:
390 host, port = uri[:cred_pos], uri[cred_pos + 1:]
407 host, port = uri[:cred_pos], uri[cred_pos + 1:]
391
408
392 return filter(None, [proto, host, port])
409 return filter(None, [proto, host, port])
393
410
394
411
395 def credentials_filter(uri):
412 def credentials_filter(uri):
396 """
413 """
397 Returns a url with removed credentials
414 Returns a url with removed credentials
398
415
399 :param uri:
416 :param uri:
400 """
417 """
401
418
402 uri = uri_filter(uri)
419 uri = uri_filter(uri)
403 #check if we have port
420 #check if we have port
404 if len(uri) > 2 and uri[2]:
421 if len(uri) > 2 and uri[2]:
405 uri[2] = ':' + uri[2]
422 uri[2] = ':' + uri[2]
406
423
407 return ''.join(uri)
424 return ''.join(uri)
408
425
409
426
410 def get_changeset_safe(repo, rev):
427 def get_changeset_safe(repo, rev):
411 """
428 """
412 Safe version of get_changeset if this changeset doesn't exists for a
429 Safe version of get_changeset if this changeset doesn't exists for a
413 repo it returns a Dummy one instead
430 repo it returns a Dummy one instead
414
431
415 :param repo:
432 :param repo:
416 :param rev:
433 :param rev:
417 """
434 """
418 from rhodecode.lib.vcs.backends.base import BaseRepository
435 from rhodecode.lib.vcs.backends.base import BaseRepository
419 from rhodecode.lib.vcs.exceptions import RepositoryError
436 from rhodecode.lib.vcs.exceptions import RepositoryError
420 from rhodecode.lib.vcs.backends.base import EmptyChangeset
437 from rhodecode.lib.vcs.backends.base import EmptyChangeset
421 if not isinstance(repo, BaseRepository):
438 if not isinstance(repo, BaseRepository):
422 raise Exception('You must pass an Repository '
439 raise Exception('You must pass an Repository '
423 'object as first argument got %s', type(repo))
440 'object as first argument got %s', type(repo))
424
441
425 try:
442 try:
426 cs = repo.get_changeset(rev)
443 cs = repo.get_changeset(rev)
427 except RepositoryError:
444 except RepositoryError:
428 cs = EmptyChangeset(requested_revision=rev)
445 cs = EmptyChangeset(requested_revision=rev)
429 return cs
446 return cs
430
447
431
448
432 def datetime_to_time(dt):
449 def datetime_to_time(dt):
433 if dt:
450 if dt:
434 return time.mktime(dt.timetuple())
451 return time.mktime(dt.timetuple())
435
452
436
453
437 def time_to_datetime(tm):
454 def time_to_datetime(tm):
438 if tm:
455 if tm:
439 if isinstance(tm, basestring):
456 if isinstance(tm, basestring):
440 try:
457 try:
441 tm = float(tm)
458 tm = float(tm)
442 except ValueError:
459 except ValueError:
443 return
460 return
444 return datetime.datetime.fromtimestamp(tm)
461 return datetime.datetime.fromtimestamp(tm)
445
462
446 MENTIONS_REGEX = r'(?:^@|\s@)([a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+)(?:\s{1})'
463 MENTIONS_REGEX = r'(?:^@|\s@)([a-zA-Z0-9]{1}[a-zA-Z0-9\-\_\.]+)(?:\s{1})'
447
464
448
465
449 def extract_mentioned_users(s):
466 def extract_mentioned_users(s):
450 """
467 """
451 Returns unique usernames from given string s that have @mention
468 Returns unique usernames from given string s that have @mention
452
469
453 :param s: string to get mentions
470 :param s: string to get mentions
454 """
471 """
455 usrs = set()
472 usrs = set()
456 for username in re.findall(MENTIONS_REGEX, s):
473 for username in re.findall(MENTIONS_REGEX, s):
457 usrs.add(username)
474 usrs.add(username)
458
475
459 return sorted(list(usrs), key=lambda k: k.lower())
476 return sorted(list(usrs), key=lambda k: k.lower())
460
477
461
478
462 class AttributeDict(dict):
479 class AttributeDict(dict):
463 def __getattr__(self, attr):
480 def __getattr__(self, attr):
464 return self.get(attr, None)
481 return self.get(attr, None)
465 __setattr__ = dict.__setitem__
482 __setattr__ = dict.__setitem__
466 __delattr__ = dict.__delitem__
483 __delattr__ = dict.__delitem__
@@ -1,460 +1,461 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 # original copyright: 2007-2008 by Armin Ronacher
2 # original copyright: 2007-2008 by Armin Ronacher
3 # licensed under the BSD license.
3 # licensed under the BSD license.
4
4
5 import re
5 import re
6 import difflib
6 import difflib
7 import logging
7 import logging
8
8
9 from difflib import unified_diff
9 from difflib import unified_diff
10 from itertools import tee, imap
10 from itertools import tee, imap
11
11
12 from mercurial.match import match
12 from mercurial.match import match
13
13
14 from rhodecode.lib.vcs.exceptions import VCSError
14 from rhodecode.lib.vcs.exceptions import VCSError
15 from rhodecode.lib.vcs.nodes import FileNode, NodeError
15 from rhodecode.lib.vcs.nodes import FileNode, NodeError
16 from rhodecode.lib.vcs.utils import safe_unicode
16
17
17
18
18 def get_udiff(filenode_old, filenode_new, show_whitespace=True):
19 def get_udiff(filenode_old, filenode_new, show_whitespace=True):
19 """
20 """
20 Returns unified diff between given ``filenode_old`` and ``filenode_new``.
21 Returns unified diff between given ``filenode_old`` and ``filenode_new``.
21 """
22 """
22 try:
23 try:
23 filenode_old_date = filenode_old.changeset.date
24 filenode_old_date = filenode_old.changeset.date
24 except NodeError:
25 except NodeError:
25 filenode_old_date = None
26 filenode_old_date = None
26
27
27 try:
28 try:
28 filenode_new_date = filenode_new.changeset.date
29 filenode_new_date = filenode_new.changeset.date
29 except NodeError:
30 except NodeError:
30 filenode_new_date = None
31 filenode_new_date = None
31
32
32 for filenode in (filenode_old, filenode_new):
33 for filenode in (filenode_old, filenode_new):
33 if not isinstance(filenode, FileNode):
34 if not isinstance(filenode, FileNode):
34 raise VCSError("Given object should be FileNode object, not %s"
35 raise VCSError("Given object should be FileNode object, not %s"
35 % filenode.__class__)
36 % filenode.__class__)
36
37
37 if filenode_old_date and filenode_new_date:
38 if filenode_old_date and filenode_new_date:
38 if not filenode_old_date < filenode_new_date:
39 if not filenode_old_date < filenode_new_date:
39 logging.debug("Generating udiff for filenodes with not increasing "
40 logging.debug("Generating udiff for filenodes with not increasing "
40 "dates")
41 "dates")
41
42
42 vcs_udiff = unified_diff(filenode_old.content.splitlines(True),
43 vcs_udiff = unified_diff(filenode_old.content.splitlines(True),
43 filenode_new.content.splitlines(True),
44 filenode_new.content.splitlines(True),
44 filenode_old.name,
45 filenode_old.name,
45 filenode_new.name,
46 filenode_new.name,
46 filenode_old_date,
47 filenode_old_date,
47 filenode_old_date)
48 filenode_old_date)
48 return vcs_udiff
49 return vcs_udiff
49
50
50
51
51 def get_gitdiff(filenode_old, filenode_new, ignore_whitespace=True):
52 def get_gitdiff(filenode_old, filenode_new, ignore_whitespace=True):
52 """
53 """
53 Returns git style diff between given ``filenode_old`` and ``filenode_new``.
54 Returns git style diff between given ``filenode_old`` and ``filenode_new``.
54
55
55 :param ignore_whitespace: ignore whitespaces in diff
56 :param ignore_whitespace: ignore whitespaces in diff
56 """
57 """
57
58
58 for filenode in (filenode_old, filenode_new):
59 for filenode in (filenode_old, filenode_new):
59 if not isinstance(filenode, FileNode):
60 if not isinstance(filenode, FileNode):
60 raise VCSError("Given object should be FileNode object, not %s"
61 raise VCSError("Given object should be FileNode object, not %s"
61 % filenode.__class__)
62 % filenode.__class__)
62
63
63 old_raw_id = getattr(filenode_old.changeset, 'raw_id', '0' * 40)
64 old_raw_id = getattr(filenode_old.changeset, 'raw_id', '0' * 40)
64 new_raw_id = getattr(filenode_new.changeset, 'raw_id', '0' * 40)
65 new_raw_id = getattr(filenode_new.changeset, 'raw_id', '0' * 40)
65
66
66 repo = filenode_new.changeset.repository
67 repo = filenode_new.changeset.repository
67 vcs_gitdiff = repo._get_diff(old_raw_id, new_raw_id, filenode_new.path,
68 vcs_gitdiff = repo._get_diff(old_raw_id, new_raw_id, filenode_new.path,
68 ignore_whitespace)
69 ignore_whitespace)
69
70
70 return vcs_gitdiff
71 return vcs_gitdiff
71
72
72
73
73 class DiffProcessor(object):
74 class DiffProcessor(object):
74 """
75 """
75 Give it a unified diff and it returns a list of the files that were
76 Give it a unified diff and it returns a list of the files that were
76 mentioned in the diff together with a dict of meta information that
77 mentioned in the diff together with a dict of meta information that
77 can be used to render it in a HTML template.
78 can be used to render it in a HTML template.
78 """
79 """
79 _chunk_re = re.compile(r'@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@(.*)')
80 _chunk_re = re.compile(r'@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@(.*)')
80
81
81 def __init__(self, diff, differ='diff', format='udiff'):
82 def __init__(self, diff, differ='diff', format='udiff'):
82 """
83 """
83 :param diff: a text in diff format or generator
84 :param diff: a text in diff format or generator
84 :param format: format of diff passed, `udiff` or `gitdiff`
85 :param format: format of diff passed, `udiff` or `gitdiff`
85 """
86 """
86 if isinstance(diff, basestring):
87 if isinstance(diff, basestring):
87 diff = [diff]
88 diff = [diff]
88
89
89 self.__udiff = diff
90 self.__udiff = diff
90 self.__format = format
91 self.__format = format
91 self.adds = 0
92 self.adds = 0
92 self.removes = 0
93 self.removes = 0
93
94
94 if isinstance(self.__udiff, basestring):
95 if isinstance(self.__udiff, basestring):
95 self.lines = iter(self.__udiff.splitlines(1))
96 self.lines = iter(self.__udiff.splitlines(1))
96
97
97 elif self.__format == 'gitdiff':
98 elif self.__format == 'gitdiff':
98 udiff_copy = self.copy_iterator()
99 udiff_copy = self.copy_iterator()
99 self.lines = imap(self.escaper, self._parse_gitdiff(udiff_copy))
100 self.lines = imap(self.escaper, self._parse_gitdiff(udiff_copy))
100 else:
101 else:
101 udiff_copy = self.copy_iterator()
102 udiff_copy = self.copy_iterator()
102 self.lines = imap(self.escaper, udiff_copy)
103 self.lines = imap(self.escaper, udiff_copy)
103
104
104 # Select a differ.
105 # Select a differ.
105 if differ == 'difflib':
106 if differ == 'difflib':
106 self.differ = self._highlight_line_difflib
107 self.differ = self._highlight_line_difflib
107 else:
108 else:
108 self.differ = self._highlight_line_udiff
109 self.differ = self._highlight_line_udiff
109
110
110 def escaper(self, string):
111 def escaper(self, string):
111 return string.replace('<', '&lt;').replace('>', '&gt;')
112 return string.replace('<', '&lt;').replace('>', '&gt;')
112
113
113 def copy_iterator(self):
114 def copy_iterator(self):
114 """
115 """
115 make a fresh copy of generator, we should not iterate thru
116 make a fresh copy of generator, we should not iterate thru
116 an original as it's needed for repeating operations on
117 an original as it's needed for repeating operations on
117 this instance of DiffProcessor
118 this instance of DiffProcessor
118 """
119 """
119 self.__udiff, iterator_copy = tee(self.__udiff)
120 self.__udiff, iterator_copy = tee(self.__udiff)
120 return iterator_copy
121 return iterator_copy
121
122
122 def _extract_rev(self, line1, line2):
123 def _extract_rev(self, line1, line2):
123 """
124 """
124 Extract the filename and revision hint from a line.
125 Extract the filename and revision hint from a line.
125 """
126 """
126
127
127 try:
128 try:
128 if line1.startswith('--- ') and line2.startswith('+++ '):
129 if line1.startswith('--- ') and line2.startswith('+++ '):
129 l1 = line1[4:].split(None, 1)
130 l1 = line1[4:].split(None, 1)
130 old_filename = l1[0].lstrip('a/') if len(l1) >= 1 else None
131 old_filename = l1[0].lstrip('a/') if len(l1) >= 1 else None
131 old_rev = l1[1] if len(l1) == 2 else 'old'
132 old_rev = l1[1] if len(l1) == 2 else 'old'
132
133
133 l2 = line2[4:].split(None, 1)
134 l2 = line2[4:].split(None, 1)
134 new_filename = l2[0].lstrip('b/') if len(l1) >= 1 else None
135 new_filename = l2[0].lstrip('b/') if len(l1) >= 1 else None
135 new_rev = l2[1] if len(l2) == 2 else 'new'
136 new_rev = l2[1] if len(l2) == 2 else 'new'
136
137
137 filename = old_filename if (old_filename !=
138 filename = old_filename if (old_filename !=
138 'dev/null') else new_filename
139 'dev/null') else new_filename
139
140
140 return filename, new_rev, old_rev
141 return filename, new_rev, old_rev
141 except (ValueError, IndexError):
142 except (ValueError, IndexError):
142 pass
143 pass
143
144
144 return None, None, None
145 return None, None, None
145
146
146 def _parse_gitdiff(self, diffiterator):
147 def _parse_gitdiff(self, diffiterator):
147 def line_decoder(l):
148 def line_decoder(l):
148 if l.startswith('+') and not l.startswith('+++'):
149 if l.startswith('+') and not l.startswith('+++'):
149 self.adds += 1
150 self.adds += 1
150 elif l.startswith('-') and not l.startswith('---'):
151 elif l.startswith('-') and not l.startswith('---'):
151 self.removes += 1
152 self.removes += 1
152 return l.decode('utf8', 'replace')
153 return safe_unicode(l)
153
154
154 output = list(diffiterator)
155 output = list(diffiterator)
155 size = len(output)
156 size = len(output)
156
157
157 if size == 2:
158 if size == 2:
158 l = []
159 l = []
159 l.extend([output[0]])
160 l.extend([output[0]])
160 l.extend(output[1].splitlines(1))
161 l.extend(output[1].splitlines(1))
161 return map(line_decoder, l)
162 return map(line_decoder, l)
162 elif size == 1:
163 elif size == 1:
163 return map(line_decoder, output[0].splitlines(1))
164 return map(line_decoder, output[0].splitlines(1))
164 elif size == 0:
165 elif size == 0:
165 return []
166 return []
166
167
167 raise Exception('wrong size of diff %s' % size)
168 raise Exception('wrong size of diff %s' % size)
168
169
169 def _highlight_line_difflib(self, line, next):
170 def _highlight_line_difflib(self, line, next):
170 """
171 """
171 Highlight inline changes in both lines.
172 Highlight inline changes in both lines.
172 """
173 """
173
174
174 if line['action'] == 'del':
175 if line['action'] == 'del':
175 old, new = line, next
176 old, new = line, next
176 else:
177 else:
177 old, new = next, line
178 old, new = next, line
178
179
179 oldwords = re.split(r'(\W)', old['line'])
180 oldwords = re.split(r'(\W)', old['line'])
180 newwords = re.split(r'(\W)', new['line'])
181 newwords = re.split(r'(\W)', new['line'])
181
182
182 sequence = difflib.SequenceMatcher(None, oldwords, newwords)
183 sequence = difflib.SequenceMatcher(None, oldwords, newwords)
183
184
184 oldfragments, newfragments = [], []
185 oldfragments, newfragments = [], []
185 for tag, i1, i2, j1, j2 in sequence.get_opcodes():
186 for tag, i1, i2, j1, j2 in sequence.get_opcodes():
186 oldfrag = ''.join(oldwords[i1:i2])
187 oldfrag = ''.join(oldwords[i1:i2])
187 newfrag = ''.join(newwords[j1:j2])
188 newfrag = ''.join(newwords[j1:j2])
188 if tag != 'equal':
189 if tag != 'equal':
189 if oldfrag:
190 if oldfrag:
190 oldfrag = '<del>%s</del>' % oldfrag
191 oldfrag = '<del>%s</del>' % oldfrag
191 if newfrag:
192 if newfrag:
192 newfrag = '<ins>%s</ins>' % newfrag
193 newfrag = '<ins>%s</ins>' % newfrag
193 oldfragments.append(oldfrag)
194 oldfragments.append(oldfrag)
194 newfragments.append(newfrag)
195 newfragments.append(newfrag)
195
196
196 old['line'] = "".join(oldfragments)
197 old['line'] = "".join(oldfragments)
197 new['line'] = "".join(newfragments)
198 new['line'] = "".join(newfragments)
198
199
199 def _highlight_line_udiff(self, line, next):
200 def _highlight_line_udiff(self, line, next):
200 """
201 """
201 Highlight inline changes in both lines.
202 Highlight inline changes in both lines.
202 """
203 """
203 start = 0
204 start = 0
204 limit = min(len(line['line']), len(next['line']))
205 limit = min(len(line['line']), len(next['line']))
205 while start < limit and line['line'][start] == next['line'][start]:
206 while start < limit and line['line'][start] == next['line'][start]:
206 start += 1
207 start += 1
207 end = -1
208 end = -1
208 limit -= start
209 limit -= start
209 while -end <= limit and line['line'][end] == next['line'][end]:
210 while -end <= limit and line['line'][end] == next['line'][end]:
210 end -= 1
211 end -= 1
211 end += 1
212 end += 1
212 if start or end:
213 if start or end:
213 def do(l):
214 def do(l):
214 last = end + len(l['line'])
215 last = end + len(l['line'])
215 if l['action'] == 'add':
216 if l['action'] == 'add':
216 tag = 'ins'
217 tag = 'ins'
217 else:
218 else:
218 tag = 'del'
219 tag = 'del'
219 l['line'] = '%s<%s>%s</%s>%s' % (
220 l['line'] = '%s<%s>%s</%s>%s' % (
220 l['line'][:start],
221 l['line'][:start],
221 tag,
222 tag,
222 l['line'][start:last],
223 l['line'][start:last],
223 tag,
224 tag,
224 l['line'][last:]
225 l['line'][last:]
225 )
226 )
226 do(line)
227 do(line)
227 do(next)
228 do(next)
228
229
229 def _parse_udiff(self):
230 def _parse_udiff(self):
230 """
231 """
231 Parse the diff an return data for the template.
232 Parse the diff an return data for the template.
232 """
233 """
233 lineiter = self.lines
234 lineiter = self.lines
234 files = []
235 files = []
235 try:
236 try:
236 line = lineiter.next()
237 line = lineiter.next()
237 # skip first context
238 # skip first context
238 skipfirst = True
239 skipfirst = True
239 while 1:
240 while 1:
240 # continue until we found the old file
241 # continue until we found the old file
241 if not line.startswith('--- '):
242 if not line.startswith('--- '):
242 line = lineiter.next()
243 line = lineiter.next()
243 continue
244 continue
244
245
245 chunks = []
246 chunks = []
246 filename, old_rev, new_rev = \
247 filename, old_rev, new_rev = \
247 self._extract_rev(line, lineiter.next())
248 self._extract_rev(line, lineiter.next())
248 files.append({
249 files.append({
249 'filename': filename,
250 'filename': filename,
250 'old_revision': old_rev,
251 'old_revision': old_rev,
251 'new_revision': new_rev,
252 'new_revision': new_rev,
252 'chunks': chunks
253 'chunks': chunks
253 })
254 })
254
255
255 line = lineiter.next()
256 line = lineiter.next()
256 while line:
257 while line:
257 match = self._chunk_re.match(line)
258 match = self._chunk_re.match(line)
258 if not match:
259 if not match:
259 break
260 break
260
261
261 lines = []
262 lines = []
262 chunks.append(lines)
263 chunks.append(lines)
263
264
264 old_line, old_end, new_line, new_end = \
265 old_line, old_end, new_line, new_end = \
265 [int(x or 1) for x in match.groups()[:-1]]
266 [int(x or 1) for x in match.groups()[:-1]]
266 old_line -= 1
267 old_line -= 1
267 new_line -= 1
268 new_line -= 1
268 context = len(match.groups()) == 5
269 context = len(match.groups()) == 5
269 old_end += old_line
270 old_end += old_line
270 new_end += new_line
271 new_end += new_line
271
272
272 if context:
273 if context:
273 if not skipfirst:
274 if not skipfirst:
274 lines.append({
275 lines.append({
275 'old_lineno': '...',
276 'old_lineno': '...',
276 'new_lineno': '...',
277 'new_lineno': '...',
277 'action': 'context',
278 'action': 'context',
278 'line': line,
279 'line': line,
279 })
280 })
280 else:
281 else:
281 skipfirst = False
282 skipfirst = False
282
283
283 line = lineiter.next()
284 line = lineiter.next()
284 while old_line < old_end or new_line < new_end:
285 while old_line < old_end or new_line < new_end:
285 if line:
286 if line:
286 command, line = line[0], line[1:]
287 command, line = line[0], line[1:]
287 else:
288 else:
288 command = ' '
289 command = ' '
289 affects_old = affects_new = False
290 affects_old = affects_new = False
290
291
291 # ignore those if we don't expect them
292 # ignore those if we don't expect them
292 if command in '#@':
293 if command in '#@':
293 continue
294 continue
294 elif command == '+':
295 elif command == '+':
295 affects_new = True
296 affects_new = True
296 action = 'add'
297 action = 'add'
297 elif command == '-':
298 elif command == '-':
298 affects_old = True
299 affects_old = True
299 action = 'del'
300 action = 'del'
300 else:
301 else:
301 affects_old = affects_new = True
302 affects_old = affects_new = True
302 action = 'unmod'
303 action = 'unmod'
303
304
304 old_line += affects_old
305 old_line += affects_old
305 new_line += affects_new
306 new_line += affects_new
306 lines.append({
307 lines.append({
307 'old_lineno': affects_old and old_line or '',
308 'old_lineno': affects_old and old_line or '',
308 'new_lineno': affects_new and new_line or '',
309 'new_lineno': affects_new and new_line or '',
309 'action': action,
310 'action': action,
310 'line': line
311 'line': line
311 })
312 })
312 line = lineiter.next()
313 line = lineiter.next()
313
314
314 except StopIteration:
315 except StopIteration:
315 pass
316 pass
316
317
317 # highlight inline changes
318 # highlight inline changes
318 for file in files:
319 for file in files:
319 for chunk in chunks:
320 for chunk in chunks:
320 lineiter = iter(chunk)
321 lineiter = iter(chunk)
321 #first = True
322 #first = True
322 try:
323 try:
323 while 1:
324 while 1:
324 line = lineiter.next()
325 line = lineiter.next()
325 if line['action'] != 'unmod':
326 if line['action'] != 'unmod':
326 nextline = lineiter.next()
327 nextline = lineiter.next()
327 if nextline['action'] == 'unmod' or \
328 if nextline['action'] == 'unmod' or \
328 nextline['action'] == line['action']:
329 nextline['action'] == line['action']:
329 continue
330 continue
330 self.differ(line, nextline)
331 self.differ(line, nextline)
331 except StopIteration:
332 except StopIteration:
332 pass
333 pass
333
334
334 return files
335 return files
335
336
336 def prepare(self):
337 def prepare(self):
337 """
338 """
338 Prepare the passed udiff for HTML rendering. It'l return a list
339 Prepare the passed udiff for HTML rendering. It'l return a list
339 of dicts
340 of dicts
340 """
341 """
341 return self._parse_udiff()
342 return self._parse_udiff()
342
343
343 def _safe_id(self, idstring):
344 def _safe_id(self, idstring):
344 """Make a string safe for including in an id attribute.
345 """Make a string safe for including in an id attribute.
345
346
346 The HTML spec says that id attributes 'must begin with
347 The HTML spec says that id attributes 'must begin with
347 a letter ([A-Za-z]) and may be followed by any number
348 a letter ([A-Za-z]) and may be followed by any number
348 of letters, digits ([0-9]), hyphens ("-"), underscores
349 of letters, digits ([0-9]), hyphens ("-"), underscores
349 ("_"), colons (":"), and periods (".")'. These regexps
350 ("_"), colons (":"), and periods (".")'. These regexps
350 are slightly over-zealous, in that they remove colons
351 are slightly over-zealous, in that they remove colons
351 and periods unnecessarily.
352 and periods unnecessarily.
352
353
353 Whitespace is transformed into underscores, and then
354 Whitespace is transformed into underscores, and then
354 anything which is not a hyphen or a character that
355 anything which is not a hyphen or a character that
355 matches \w (alphanumerics and underscore) is removed.
356 matches \w (alphanumerics and underscore) is removed.
356
357
357 """
358 """
358 # Transform all whitespace to underscore
359 # Transform all whitespace to underscore
359 idstring = re.sub(r'\s', "_", '%s' % idstring)
360 idstring = re.sub(r'\s', "_", '%s' % idstring)
360 # Remove everything that is not a hyphen or a member of \w
361 # Remove everything that is not a hyphen or a member of \w
361 idstring = re.sub(r'(?!-)\W', "", idstring).lower()
362 idstring = re.sub(r'(?!-)\W', "", idstring).lower()
362 return idstring
363 return idstring
363
364
364 def raw_diff(self):
365 def raw_diff(self):
365 """
366 """
366 Returns raw string as udiff
367 Returns raw string as udiff
367 """
368 """
368 udiff_copy = self.copy_iterator()
369 udiff_copy = self.copy_iterator()
369 if self.__format == 'gitdiff':
370 if self.__format == 'gitdiff':
370 udiff_copy = self._parse_gitdiff(udiff_copy)
371 udiff_copy = self._parse_gitdiff(udiff_copy)
371 return u''.join(udiff_copy)
372 return u''.join(udiff_copy)
372
373
373 def as_html(self, table_class='code-difftable', line_class='line',
374 def as_html(self, table_class='code-difftable', line_class='line',
374 new_lineno_class='lineno old', old_lineno_class='lineno new',
375 new_lineno_class='lineno old', old_lineno_class='lineno new',
375 code_class='code'):
376 code_class='code'):
376 """
377 """
377 Return udiff as html table with customized css classes
378 Return udiff as html table with customized css classes
378 """
379 """
379 def _link_to_if(condition, label, url):
380 def _link_to_if(condition, label, url):
380 """
381 """
381 Generates a link if condition is meet or just the label if not.
382 Generates a link if condition is meet or just the label if not.
382 """
383 """
383
384
384 if condition:
385 if condition:
385 return '''<a href="%(url)s">%(label)s</a>''' % {'url': url,
386 return '''<a href="%(url)s">%(label)s</a>''' % {'url': url,
386 'label': label}
387 'label': label}
387 else:
388 else:
388 return label
389 return label
389 diff_lines = self.prepare()
390 diff_lines = self.prepare()
390 _html_empty = True
391 _html_empty = True
391 _html = []
392 _html = []
392 _html.append('''<table class="%(table_class)s">\n''' \
393 _html.append('''<table class="%(table_class)s">\n''' \
393 % {'table_class': table_class})
394 % {'table_class': table_class})
394 for diff in diff_lines:
395 for diff in diff_lines:
395 for line in diff['chunks']:
396 for line in diff['chunks']:
396 _html_empty = False
397 _html_empty = False
397 for change in line:
398 for change in line:
398 _html.append('''<tr class="%(line_class)s %(action)s">\n''' \
399 _html.append('''<tr class="%(line_class)s %(action)s">\n''' \
399 % {'line_class': line_class,
400 % {'line_class': line_class,
400 'action': change['action']})
401 'action': change['action']})
401 anchor_old_id = ''
402 anchor_old_id = ''
402 anchor_new_id = ''
403 anchor_new_id = ''
403 anchor_old = "%(filename)s_o%(oldline_no)s" % \
404 anchor_old = "%(filename)s_o%(oldline_no)s" % \
404 {'filename': self._safe_id(diff['filename']),
405 {'filename': self._safe_id(diff['filename']),
405 'oldline_no': change['old_lineno']}
406 'oldline_no': change['old_lineno']}
406 anchor_new = "%(filename)s_n%(oldline_no)s" % \
407 anchor_new = "%(filename)s_n%(oldline_no)s" % \
407 {'filename': self._safe_id(diff['filename']),
408 {'filename': self._safe_id(diff['filename']),
408 'oldline_no': change['new_lineno']}
409 'oldline_no': change['new_lineno']}
409 cond_old = change['old_lineno'] != '...' and \
410 cond_old = change['old_lineno'] != '...' and \
410 change['old_lineno']
411 change['old_lineno']
411 cond_new = change['new_lineno'] != '...' and \
412 cond_new = change['new_lineno'] != '...' and \
412 change['new_lineno']
413 change['new_lineno']
413 if cond_old:
414 if cond_old:
414 anchor_old_id = 'id="%s"' % anchor_old
415 anchor_old_id = 'id="%s"' % anchor_old
415 if cond_new:
416 if cond_new:
416 anchor_new_id = 'id="%s"' % anchor_new
417 anchor_new_id = 'id="%s"' % anchor_new
417 ###########################################################
418 ###########################################################
418 # OLD LINE NUMBER
419 # OLD LINE NUMBER
419 ###########################################################
420 ###########################################################
420 _html.append('''\t<td %(a_id)s class="%(old_lineno_cls)s">''' \
421 _html.append('''\t<td %(a_id)s class="%(old_lineno_cls)s">''' \
421 % {'a_id': anchor_old_id,
422 % {'a_id': anchor_old_id,
422 'old_lineno_cls': old_lineno_class})
423 'old_lineno_cls': old_lineno_class})
423
424
424 _html.append('''<pre>%(link)s</pre>''' \
425 _html.append('''<pre>%(link)s</pre>''' \
425 % {'link':
426 % {'link':
426 _link_to_if(cond_old, change['old_lineno'], '#%s' \
427 _link_to_if(cond_old, change['old_lineno'], '#%s' \
427 % anchor_old)})
428 % anchor_old)})
428 _html.append('''</td>\n''')
429 _html.append('''</td>\n''')
429 ###########################################################
430 ###########################################################
430 # NEW LINE NUMBER
431 # NEW LINE NUMBER
431 ###########################################################
432 ###########################################################
432
433
433 _html.append('''\t<td %(a_id)s class="%(new_lineno_cls)s">''' \
434 _html.append('''\t<td %(a_id)s class="%(new_lineno_cls)s">''' \
434 % {'a_id': anchor_new_id,
435 % {'a_id': anchor_new_id,
435 'new_lineno_cls': new_lineno_class})
436 'new_lineno_cls': new_lineno_class})
436
437
437 _html.append('''<pre>%(link)s</pre>''' \
438 _html.append('''<pre>%(link)s</pre>''' \
438 % {'link':
439 % {'link':
439 _link_to_if(cond_new, change['new_lineno'], '#%s' \
440 _link_to_if(cond_new, change['new_lineno'], '#%s' \
440 % anchor_new)})
441 % anchor_new)})
441 _html.append('''</td>\n''')
442 _html.append('''</td>\n''')
442 ###########################################################
443 ###########################################################
443 # CODE
444 # CODE
444 ###########################################################
445 ###########################################################
445 _html.append('''\t<td class="%(code_class)s">''' \
446 _html.append('''\t<td class="%(code_class)s">''' \
446 % {'code_class': code_class})
447 % {'code_class': code_class})
447 _html.append('''\n\t\t<pre>%(code)s</pre>\n''' \
448 _html.append('''\n\t\t<pre>%(code)s</pre>\n''' \
448 % {'code': change['line']})
449 % {'code': change['line']})
449 _html.append('''\t</td>''')
450 _html.append('''\t</td>''')
450 _html.append('''\n</tr>\n''')
451 _html.append('''\n</tr>\n''')
451 _html.append('''</table>''')
452 _html.append('''</table>''')
452 if _html_empty:
453 if _html_empty:
453 return None
454 return None
454 return ''.join(_html)
455 return ''.join(_html)
455
456
456 def stat(self):
457 def stat(self):
457 """
458 """
458 Returns tuple of adde,and removed lines for this instance
459 Returns tuple of adde,and removed lines for this instance
459 """
460 """
460 return self.adds, self.removes
461 return self.adds, self.removes
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
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