##// END OF EJS Templates
new patch function, and urls schema....
marcink -
r2996:ebe3e388 beta
parent child Browse files
Show More
@@ -1,590 +1,607 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
145 with rmap.submapper(path_prefix=ADMIN_PREFIX,
145 with rmap.submapper(path_prefix=ADMIN_PREFIX,
146 controller='admin/repos_groups') as m:
146 controller='admin/repos_groups') as m:
147 m.connect("repos_groups", "/repos_groups",
147 m.connect("repos_groups", "/repos_groups",
148 action="create", conditions=dict(method=["POST"]))
148 action="create", conditions=dict(method=["POST"]))
149 m.connect("repos_groups", "/repos_groups",
149 m.connect("repos_groups", "/repos_groups",
150 action="index", conditions=dict(method=["GET"]))
150 action="index", conditions=dict(method=["GET"]))
151 m.connect("formatted_repos_groups", "/repos_groups.{format}",
151 m.connect("formatted_repos_groups", "/repos_groups.{format}",
152 action="index", conditions=dict(method=["GET"]))
152 action="index", conditions=dict(method=["GET"]))
153 m.connect("new_repos_group", "/repos_groups/new",
153 m.connect("new_repos_group", "/repos_groups/new",
154 action="new", conditions=dict(method=["GET"]))
154 action="new", conditions=dict(method=["GET"]))
155 m.connect("formatted_new_repos_group", "/repos_groups/new.{format}",
155 m.connect("formatted_new_repos_group", "/repos_groups/new.{format}",
156 action="new", conditions=dict(method=["GET"]))
156 action="new", conditions=dict(method=["GET"]))
157 m.connect("update_repos_group", "/repos_groups/{id}",
157 m.connect("update_repos_group", "/repos_groups/{id}",
158 action="update", conditions=dict(method=["PUT"],
158 action="update", conditions=dict(method=["PUT"],
159 function=check_int))
159 function=check_int))
160 m.connect("delete_repos_group", "/repos_groups/{id}",
160 m.connect("delete_repos_group", "/repos_groups/{id}",
161 action="delete", conditions=dict(method=["DELETE"],
161 action="delete", conditions=dict(method=["DELETE"],
162 function=check_int))
162 function=check_int))
163 m.connect("edit_repos_group", "/repos_groups/{id:.*?}/edit",
163 m.connect("edit_repos_group", "/repos_groups/{id:.*?}/edit",
164 action="edit", conditions=dict(method=["GET"],))
164 action="edit", conditions=dict(method=["GET"],))
165 m.connect("formatted_edit_repos_group",
165 m.connect("formatted_edit_repos_group",
166 "/repos_groups/{id}.{format}/edit",
166 "/repos_groups/{id}.{format}/edit",
167 action="edit", conditions=dict(method=["GET"],
167 action="edit", conditions=dict(method=["GET"],
168 function=check_int))
168 function=check_int))
169 m.connect("repos_group", "/repos_groups/{id}",
169 m.connect("repos_group", "/repos_groups/{id}",
170 action="show", conditions=dict(method=["GET"],
170 action="show", conditions=dict(method=["GET"],
171 function=check_int))
171 function=check_int))
172 m.connect("formatted_repos_group", "/repos_groups/{id}.{format}",
172 m.connect("formatted_repos_group", "/repos_groups/{id}.{format}",
173 action="show", conditions=dict(method=["GET"],
173 action="show", conditions=dict(method=["GET"],
174 function=check_int))
174 function=check_int))
175 # ajax delete repos group perm user
175 # ajax delete repos group perm user
176 m.connect('delete_repos_group_user_perm',
176 m.connect('delete_repos_group_user_perm',
177 "/delete_repos_group_user_perm/{group_name:.*}",
177 "/delete_repos_group_user_perm/{group_name:.*}",
178 action="delete_repos_group_user_perm",
178 action="delete_repos_group_user_perm",
179 conditions=dict(method=["DELETE"], function=check_group))
179 conditions=dict(method=["DELETE"], function=check_group))
180
180
181 # ajax delete repos group perm users_group
181 # ajax delete repos group perm users_group
182 m.connect('delete_repos_group_users_group_perm',
182 m.connect('delete_repos_group_users_group_perm',
183 "/delete_repos_group_users_group_perm/{group_name:.*}",
183 "/delete_repos_group_users_group_perm/{group_name:.*}",
184 action="delete_repos_group_users_group_perm",
184 action="delete_repos_group_users_group_perm",
185 conditions=dict(method=["DELETE"], function=check_group))
185 conditions=dict(method=["DELETE"], function=check_group))
186
186
187 #ADMIN USER REST ROUTES
187 #ADMIN USER REST ROUTES
188 with rmap.submapper(path_prefix=ADMIN_PREFIX,
188 with rmap.submapper(path_prefix=ADMIN_PREFIX,
189 controller='admin/users') as m:
189 controller='admin/users') as m:
190 m.connect("users", "/users",
190 m.connect("users", "/users",
191 action="create", conditions=dict(method=["POST"]))
191 action="create", conditions=dict(method=["POST"]))
192 m.connect("users", "/users",
192 m.connect("users", "/users",
193 action="index", conditions=dict(method=["GET"]))
193 action="index", conditions=dict(method=["GET"]))
194 m.connect("formatted_users", "/users.{format}",
194 m.connect("formatted_users", "/users.{format}",
195 action="index", conditions=dict(method=["GET"]))
195 action="index", conditions=dict(method=["GET"]))
196 m.connect("new_user", "/users/new",
196 m.connect("new_user", "/users/new",
197 action="new", conditions=dict(method=["GET"]))
197 action="new", conditions=dict(method=["GET"]))
198 m.connect("formatted_new_user", "/users/new.{format}",
198 m.connect("formatted_new_user", "/users/new.{format}",
199 action="new", conditions=dict(method=["GET"]))
199 action="new", conditions=dict(method=["GET"]))
200 m.connect("update_user", "/users/{id}",
200 m.connect("update_user", "/users/{id}",
201 action="update", conditions=dict(method=["PUT"]))
201 action="update", conditions=dict(method=["PUT"]))
202 m.connect("delete_user", "/users/{id}",
202 m.connect("delete_user", "/users/{id}",
203 action="delete", conditions=dict(method=["DELETE"]))
203 action="delete", conditions=dict(method=["DELETE"]))
204 m.connect("edit_user", "/users/{id}/edit",
204 m.connect("edit_user", "/users/{id}/edit",
205 action="edit", conditions=dict(method=["GET"]))
205 action="edit", conditions=dict(method=["GET"]))
206 m.connect("formatted_edit_user",
206 m.connect("formatted_edit_user",
207 "/users/{id}.{format}/edit",
207 "/users/{id}.{format}/edit",
208 action="edit", conditions=dict(method=["GET"]))
208 action="edit", conditions=dict(method=["GET"]))
209 m.connect("user", "/users/{id}",
209 m.connect("user", "/users/{id}",
210 action="show", conditions=dict(method=["GET"]))
210 action="show", conditions=dict(method=["GET"]))
211 m.connect("formatted_user", "/users/{id}.{format}",
211 m.connect("formatted_user", "/users/{id}.{format}",
212 action="show", conditions=dict(method=["GET"]))
212 action="show", conditions=dict(method=["GET"]))
213
213
214 #EXTRAS USER ROUTES
214 #EXTRAS USER ROUTES
215 m.connect("user_perm", "/users_perm/{id}",
215 m.connect("user_perm", "/users_perm/{id}",
216 action="update_perm", conditions=dict(method=["PUT"]))
216 action="update_perm", conditions=dict(method=["PUT"]))
217 m.connect("user_emails", "/users_emails/{id}",
217 m.connect("user_emails", "/users_emails/{id}",
218 action="add_email", conditions=dict(method=["PUT"]))
218 action="add_email", conditions=dict(method=["PUT"]))
219 m.connect("user_emails_delete", "/users_emails/{id}",
219 m.connect("user_emails_delete", "/users_emails/{id}",
220 action="delete_email", conditions=dict(method=["DELETE"]))
220 action="delete_email", conditions=dict(method=["DELETE"]))
221
221
222 #ADMIN USERS GROUPS REST ROUTES
222 #ADMIN USERS GROUPS REST ROUTES
223 with rmap.submapper(path_prefix=ADMIN_PREFIX,
223 with rmap.submapper(path_prefix=ADMIN_PREFIX,
224 controller='admin/users_groups') as m:
224 controller='admin/users_groups') as m:
225 m.connect("users_groups", "/users_groups",
225 m.connect("users_groups", "/users_groups",
226 action="create", conditions=dict(method=["POST"]))
226 action="create", conditions=dict(method=["POST"]))
227 m.connect("users_groups", "/users_groups",
227 m.connect("users_groups", "/users_groups",
228 action="index", conditions=dict(method=["GET"]))
228 action="index", conditions=dict(method=["GET"]))
229 m.connect("formatted_users_groups", "/users_groups.{format}",
229 m.connect("formatted_users_groups", "/users_groups.{format}",
230 action="index", conditions=dict(method=["GET"]))
230 action="index", conditions=dict(method=["GET"]))
231 m.connect("new_users_group", "/users_groups/new",
231 m.connect("new_users_group", "/users_groups/new",
232 action="new", conditions=dict(method=["GET"]))
232 action="new", conditions=dict(method=["GET"]))
233 m.connect("formatted_new_users_group", "/users_groups/new.{format}",
233 m.connect("formatted_new_users_group", "/users_groups/new.{format}",
234 action="new", conditions=dict(method=["GET"]))
234 action="new", conditions=dict(method=["GET"]))
235 m.connect("update_users_group", "/users_groups/{id}",
235 m.connect("update_users_group", "/users_groups/{id}",
236 action="update", conditions=dict(method=["PUT"]))
236 action="update", conditions=dict(method=["PUT"]))
237 m.connect("delete_users_group", "/users_groups/{id}",
237 m.connect("delete_users_group", "/users_groups/{id}",
238 action="delete", conditions=dict(method=["DELETE"]))
238 action="delete", conditions=dict(method=["DELETE"]))
239 m.connect("edit_users_group", "/users_groups/{id}/edit",
239 m.connect("edit_users_group", "/users_groups/{id}/edit",
240 action="edit", conditions=dict(method=["GET"]))
240 action="edit", conditions=dict(method=["GET"]))
241 m.connect("formatted_edit_users_group",
241 m.connect("formatted_edit_users_group",
242 "/users_groups/{id}.{format}/edit",
242 "/users_groups/{id}.{format}/edit",
243 action="edit", conditions=dict(method=["GET"]))
243 action="edit", conditions=dict(method=["GET"]))
244 m.connect("users_group", "/users_groups/{id}",
244 m.connect("users_group", "/users_groups/{id}",
245 action="show", conditions=dict(method=["GET"]))
245 action="show", conditions=dict(method=["GET"]))
246 m.connect("formatted_users_group", "/users_groups/{id}.{format}",
246 m.connect("formatted_users_group", "/users_groups/{id}.{format}",
247 action="show", conditions=dict(method=["GET"]))
247 action="show", conditions=dict(method=["GET"]))
248
248
249 #EXTRAS USER ROUTES
249 #EXTRAS USER ROUTES
250 m.connect("users_group_perm", "/users_groups_perm/{id}",
250 m.connect("users_group_perm", "/users_groups_perm/{id}",
251 action="update_perm", conditions=dict(method=["PUT"]))
251 action="update_perm", conditions=dict(method=["PUT"]))
252
252
253 #ADMIN GROUP REST ROUTES
253 #ADMIN GROUP REST ROUTES
254 rmap.resource('group', 'groups',
254 rmap.resource('group', 'groups',
255 controller='admin/groups', path_prefix=ADMIN_PREFIX)
255 controller='admin/groups', path_prefix=ADMIN_PREFIX)
256
256
257 #ADMIN PERMISSIONS REST ROUTES
257 #ADMIN PERMISSIONS REST ROUTES
258 rmap.resource('permission', 'permissions',
258 rmap.resource('permission', 'permissions',
259 controller='admin/permissions', path_prefix=ADMIN_PREFIX)
259 controller='admin/permissions', path_prefix=ADMIN_PREFIX)
260
260
261 ##ADMIN LDAP SETTINGS
261 ##ADMIN LDAP SETTINGS
262 rmap.connect('ldap_settings', '%s/ldap' % ADMIN_PREFIX,
262 rmap.connect('ldap_settings', '%s/ldap' % ADMIN_PREFIX,
263 controller='admin/ldap_settings', action='ldap_settings',
263 controller='admin/ldap_settings', action='ldap_settings',
264 conditions=dict(method=["POST"]))
264 conditions=dict(method=["POST"]))
265
265
266 rmap.connect('ldap_home', '%s/ldap' % ADMIN_PREFIX,
266 rmap.connect('ldap_home', '%s/ldap' % ADMIN_PREFIX,
267 controller='admin/ldap_settings')
267 controller='admin/ldap_settings')
268
268
269 #ADMIN SETTINGS REST ROUTES
269 #ADMIN SETTINGS REST ROUTES
270 with rmap.submapper(path_prefix=ADMIN_PREFIX,
270 with rmap.submapper(path_prefix=ADMIN_PREFIX,
271 controller='admin/settings') as m:
271 controller='admin/settings') as m:
272 m.connect("admin_settings", "/settings",
272 m.connect("admin_settings", "/settings",
273 action="create", conditions=dict(method=["POST"]))
273 action="create", conditions=dict(method=["POST"]))
274 m.connect("admin_settings", "/settings",
274 m.connect("admin_settings", "/settings",
275 action="index", conditions=dict(method=["GET"]))
275 action="index", conditions=dict(method=["GET"]))
276 m.connect("formatted_admin_settings", "/settings.{format}",
276 m.connect("formatted_admin_settings", "/settings.{format}",
277 action="index", conditions=dict(method=["GET"]))
277 action="index", conditions=dict(method=["GET"]))
278 m.connect("admin_new_setting", "/settings/new",
278 m.connect("admin_new_setting", "/settings/new",
279 action="new", conditions=dict(method=["GET"]))
279 action="new", conditions=dict(method=["GET"]))
280 m.connect("formatted_admin_new_setting", "/settings/new.{format}",
280 m.connect("formatted_admin_new_setting", "/settings/new.{format}",
281 action="new", conditions=dict(method=["GET"]))
281 action="new", conditions=dict(method=["GET"]))
282 m.connect("/settings/{setting_id}",
282 m.connect("/settings/{setting_id}",
283 action="update", conditions=dict(method=["PUT"]))
283 action="update", conditions=dict(method=["PUT"]))
284 m.connect("/settings/{setting_id}",
284 m.connect("/settings/{setting_id}",
285 action="delete", conditions=dict(method=["DELETE"]))
285 action="delete", conditions=dict(method=["DELETE"]))
286 m.connect("admin_edit_setting", "/settings/{setting_id}/edit",
286 m.connect("admin_edit_setting", "/settings/{setting_id}/edit",
287 action="edit", conditions=dict(method=["GET"]))
287 action="edit", conditions=dict(method=["GET"]))
288 m.connect("formatted_admin_edit_setting",
288 m.connect("formatted_admin_edit_setting",
289 "/settings/{setting_id}.{format}/edit",
289 "/settings/{setting_id}.{format}/edit",
290 action="edit", conditions=dict(method=["GET"]))
290 action="edit", conditions=dict(method=["GET"]))
291 m.connect("admin_setting", "/settings/{setting_id}",
291 m.connect("admin_setting", "/settings/{setting_id}",
292 action="show", conditions=dict(method=["GET"]))
292 action="show", conditions=dict(method=["GET"]))
293 m.connect("formatted_admin_setting", "/settings/{setting_id}.{format}",
293 m.connect("formatted_admin_setting", "/settings/{setting_id}.{format}",
294 action="show", conditions=dict(method=["GET"]))
294 action="show", conditions=dict(method=["GET"]))
295 m.connect("admin_settings_my_account", "/my_account",
295 m.connect("admin_settings_my_account", "/my_account",
296 action="my_account", conditions=dict(method=["GET"]))
296 action="my_account", conditions=dict(method=["GET"]))
297 m.connect("admin_settings_my_account_update", "/my_account_update",
297 m.connect("admin_settings_my_account_update", "/my_account_update",
298 action="my_account_update", conditions=dict(method=["PUT"]))
298 action="my_account_update", conditions=dict(method=["PUT"]))
299 m.connect("admin_settings_create_repository", "/create_repository",
299 m.connect("admin_settings_create_repository", "/create_repository",
300 action="create_repository", conditions=dict(method=["GET"]))
300 action="create_repository", conditions=dict(method=["GET"]))
301 m.connect("admin_settings_my_repos", "/my_account/repos",
301 m.connect("admin_settings_my_repos", "/my_account/repos",
302 action="my_account_my_repos", conditions=dict(method=["GET"]))
302 action="my_account_my_repos", conditions=dict(method=["GET"]))
303 m.connect("admin_settings_my_pullrequests", "/my_account/pull_requests",
303 m.connect("admin_settings_my_pullrequests", "/my_account/pull_requests",
304 action="my_account_my_pullrequests", conditions=dict(method=["GET"]))
304 action="my_account_my_pullrequests", conditions=dict(method=["GET"]))
305
305
306 #NOTIFICATION REST ROUTES
306 #NOTIFICATION REST ROUTES
307 with rmap.submapper(path_prefix=ADMIN_PREFIX,
307 with rmap.submapper(path_prefix=ADMIN_PREFIX,
308 controller='admin/notifications') as m:
308 controller='admin/notifications') as m:
309 m.connect("notifications", "/notifications",
309 m.connect("notifications", "/notifications",
310 action="create", conditions=dict(method=["POST"]))
310 action="create", conditions=dict(method=["POST"]))
311 m.connect("notifications", "/notifications",
311 m.connect("notifications", "/notifications",
312 action="index", conditions=dict(method=["GET"]))
312 action="index", conditions=dict(method=["GET"]))
313 m.connect("notifications_mark_all_read", "/notifications/mark_all_read",
313 m.connect("notifications_mark_all_read", "/notifications/mark_all_read",
314 action="mark_all_read", conditions=dict(method=["GET"]))
314 action="mark_all_read", conditions=dict(method=["GET"]))
315 m.connect("formatted_notifications", "/notifications.{format}",
315 m.connect("formatted_notifications", "/notifications.{format}",
316 action="index", conditions=dict(method=["GET"]))
316 action="index", conditions=dict(method=["GET"]))
317 m.connect("new_notification", "/notifications/new",
317 m.connect("new_notification", "/notifications/new",
318 action="new", conditions=dict(method=["GET"]))
318 action="new", conditions=dict(method=["GET"]))
319 m.connect("formatted_new_notification", "/notifications/new.{format}",
319 m.connect("formatted_new_notification", "/notifications/new.{format}",
320 action="new", conditions=dict(method=["GET"]))
320 action="new", conditions=dict(method=["GET"]))
321 m.connect("/notification/{notification_id}",
321 m.connect("/notification/{notification_id}",
322 action="update", conditions=dict(method=["PUT"]))
322 action="update", conditions=dict(method=["PUT"]))
323 m.connect("/notification/{notification_id}",
323 m.connect("/notification/{notification_id}",
324 action="delete", conditions=dict(method=["DELETE"]))
324 action="delete", conditions=dict(method=["DELETE"]))
325 m.connect("edit_notification", "/notification/{notification_id}/edit",
325 m.connect("edit_notification", "/notification/{notification_id}/edit",
326 action="edit", conditions=dict(method=["GET"]))
326 action="edit", conditions=dict(method=["GET"]))
327 m.connect("formatted_edit_notification",
327 m.connect("formatted_edit_notification",
328 "/notification/{notification_id}.{format}/edit",
328 "/notification/{notification_id}.{format}/edit",
329 action="edit", conditions=dict(method=["GET"]))
329 action="edit", conditions=dict(method=["GET"]))
330 m.connect("notification", "/notification/{notification_id}",
330 m.connect("notification", "/notification/{notification_id}",
331 action="show", conditions=dict(method=["GET"]))
331 action="show", conditions=dict(method=["GET"]))
332 m.connect("formatted_notification", "/notifications/{notification_id}.{format}",
332 m.connect("formatted_notification", "/notifications/{notification_id}.{format}",
333 action="show", conditions=dict(method=["GET"]))
333 action="show", conditions=dict(method=["GET"]))
334
334
335 #ADMIN MAIN PAGES
335 #ADMIN MAIN PAGES
336 with rmap.submapper(path_prefix=ADMIN_PREFIX,
336 with rmap.submapper(path_prefix=ADMIN_PREFIX,
337 controller='admin/admin') as m:
337 controller='admin/admin') as m:
338 m.connect('admin_home', '', action='index')
338 m.connect('admin_home', '', action='index')
339 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\. _-]*}',
340 action='add_repo')
340 action='add_repo')
341
341
342 #==========================================================================
342 #==========================================================================
343 # API V2
343 # API V2
344 #==========================================================================
344 #==========================================================================
345 with rmap.submapper(path_prefix=ADMIN_PREFIX,
345 with rmap.submapper(path_prefix=ADMIN_PREFIX,
346 controller='api/api') as m:
346 controller='api/api') as m:
347 m.connect('api', '/api')
347 m.connect('api', '/api')
348
348
349 #USER JOURNAL
349 #USER JOURNAL
350 rmap.connect('journal_my_repos', '%s/journal_my_repos' % ADMIN_PREFIX,
350 rmap.connect('journal_my_repos', '%s/journal_my_repos' % ADMIN_PREFIX,
351 controller='journal', action='index_my_repos')
351 controller='journal', action='index_my_repos')
352 rmap.connect('journal', '%s/journal' % ADMIN_PREFIX,
352 rmap.connect('journal', '%s/journal' % ADMIN_PREFIX,
353 controller='journal', action='index')
353 controller='journal', action='index')
354 rmap.connect('journal_rss', '%s/journal/rss' % ADMIN_PREFIX,
354 rmap.connect('journal_rss', '%s/journal/rss' % ADMIN_PREFIX,
355 controller='journal', action='journal_rss')
355 controller='journal', action='journal_rss')
356 rmap.connect('journal_atom', '%s/journal/atom' % ADMIN_PREFIX,
356 rmap.connect('journal_atom', '%s/journal/atom' % ADMIN_PREFIX,
357 controller='journal', action='journal_atom')
357 controller='journal', action='journal_atom')
358
358
359 rmap.connect('public_journal', '%s/public_journal' % ADMIN_PREFIX,
359 rmap.connect('public_journal', '%s/public_journal' % ADMIN_PREFIX,
360 controller='journal', action="public_journal")
360 controller='journal', action="public_journal")
361
361
362 rmap.connect('public_journal_rss', '%s/public_journal/rss' % ADMIN_PREFIX,
362 rmap.connect('public_journal_rss', '%s/public_journal/rss' % ADMIN_PREFIX,
363 controller='journal', action="public_journal_rss")
363 controller='journal', action="public_journal_rss")
364
364
365 rmap.connect('public_journal_rss_old', '%s/public_journal_rss' % ADMIN_PREFIX,
365 rmap.connect('public_journal_rss_old', '%s/public_journal_rss' % ADMIN_PREFIX,
366 controller='journal', action="public_journal_rss")
366 controller='journal', action="public_journal_rss")
367
367
368 rmap.connect('public_journal_atom',
368 rmap.connect('public_journal_atom',
369 '%s/public_journal/atom' % ADMIN_PREFIX, controller='journal',
369 '%s/public_journal/atom' % ADMIN_PREFIX, controller='journal',
370 action="public_journal_atom")
370 action="public_journal_atom")
371
371
372 rmap.connect('public_journal_atom_old',
372 rmap.connect('public_journal_atom_old',
373 '%s/public_journal_atom' % ADMIN_PREFIX, controller='journal',
373 '%s/public_journal_atom' % ADMIN_PREFIX, controller='journal',
374 action="public_journal_atom")
374 action="public_journal_atom")
375
375
376 rmap.connect('toggle_following', '%s/toggle_following' % ADMIN_PREFIX,
376 rmap.connect('toggle_following', '%s/toggle_following' % ADMIN_PREFIX,
377 controller='journal', action='toggle_following',
377 controller='journal', action='toggle_following',
378 conditions=dict(method=["POST"]))
378 conditions=dict(method=["POST"]))
379
379
380 #SEARCH
380 #SEARCH
381 rmap.connect('search', '%s/search' % ADMIN_PREFIX, controller='search',)
381 rmap.connect('search', '%s/search' % ADMIN_PREFIX, controller='search',)
382 rmap.connect('search_repo', '%s/search/{search_repo:.*}' % ADMIN_PREFIX,
382 rmap.connect('search_repo', '%s/search/{search_repo:.*}' % ADMIN_PREFIX,
383 controller='search')
383 controller='search')
384
384
385 #LOGIN/LOGOUT/REGISTER/SIGN IN
385 #LOGIN/LOGOUT/REGISTER/SIGN IN
386 rmap.connect('login_home', '%s/login' % ADMIN_PREFIX, controller='login')
386 rmap.connect('login_home', '%s/login' % ADMIN_PREFIX, controller='login')
387 rmap.connect('logout_home', '%s/logout' % ADMIN_PREFIX, controller='login',
387 rmap.connect('logout_home', '%s/logout' % ADMIN_PREFIX, controller='login',
388 action='logout')
388 action='logout')
389
389
390 rmap.connect('register', '%s/register' % ADMIN_PREFIX, controller='login',
390 rmap.connect('register', '%s/register' % ADMIN_PREFIX, controller='login',
391 action='register')
391 action='register')
392
392
393 rmap.connect('reset_password', '%s/password_reset' % ADMIN_PREFIX,
393 rmap.connect('reset_password', '%s/password_reset' % ADMIN_PREFIX,
394 controller='login', action='password_reset')
394 controller='login', action='password_reset')
395
395
396 rmap.connect('reset_password_confirmation',
396 rmap.connect('reset_password_confirmation',
397 '%s/password_reset_confirmation' % ADMIN_PREFIX,
397 '%s/password_reset_confirmation' % ADMIN_PREFIX,
398 controller='login', action='password_reset_confirmation')
398 controller='login', action='password_reset_confirmation')
399
399
400 #FEEDS
400 #FEEDS
401 rmap.connect('rss_feed_home', '/{repo_name:.*?}/feed/rss',
401 rmap.connect('rss_feed_home', '/{repo_name:.*?}/feed/rss',
402 controller='feed', action='rss',
402 controller='feed', action='rss',
403 conditions=dict(function=check_repo))
403 conditions=dict(function=check_repo))
404
404
405 rmap.connect('atom_feed_home', '/{repo_name:.*?}/feed/atom',
405 rmap.connect('atom_feed_home', '/{repo_name:.*?}/feed/atom',
406 controller='feed', action='atom',
406 controller='feed', action='atom',
407 conditions=dict(function=check_repo))
407 conditions=dict(function=check_repo))
408
408
409 #==========================================================================
409 #==========================================================================
410 # REPOSITORY ROUTES
410 # REPOSITORY ROUTES
411 #==========================================================================
411 #==========================================================================
412 rmap.connect('summary_home', '/{repo_name:.*?}',
412 rmap.connect('summary_home', '/{repo_name:.*?}',
413 controller='summary',
413 controller='summary',
414 conditions=dict(function=check_repo))
414 conditions=dict(function=check_repo))
415
415
416 rmap.connect('repos_group_home', '/{group_name:.*}',
416 rmap.connect('repos_group_home', '/{group_name:.*}',
417 controller='admin/repos_groups', action="show_by_name",
417 controller='admin/repos_groups', action="show_by_name",
418 conditions=dict(function=check_group))
418 conditions=dict(function=check_group))
419
419
420 rmap.connect('changeset_home', '/{repo_name:.*?}/changeset/{revision}',
420 rmap.connect('changeset_home', '/{repo_name:.*?}/changeset/{revision}',
421 controller='changeset', revision='tip',
421 controller='changeset', revision='tip',
422 conditions=dict(function=check_repo))
422 conditions=dict(function=check_repo))
423
423
424 #still working url for backward compat.
425 rmap.connect('raw_changeset_home_depraced',
426 '/{repo_name:.*?}/raw-changeset/{revision}',
427 controller='changeset', action='changeset_raw',
428 revision='tip', conditions=dict(function=check_repo))
429
430 ## new URLs
431 rmap.connect('changeset_raw_home',
432 '/{repo_name:.*?}/changeset-diff/{revision}',
433 controller='changeset', action='changeset_raw',
434 revision='tip', conditions=dict(function=check_repo))
435
436 rmap.connect('changeset_patch_home',
437 '/{repo_name:.*?}/changeset-patch/{revision}',
438 controller='changeset', action='changeset_patch',
439 revision='tip', conditions=dict(function=check_repo))
440
441 rmap.connect('changeset_download_home',
442 '/{repo_name:.*?}/changeset-download/{revision}',
443 controller='changeset', action='changeset_download',
444 revision='tip', conditions=dict(function=check_repo))
445
424 rmap.connect('changeset_comment',
446 rmap.connect('changeset_comment',
425 '/{repo_name:.*?}/changeset/{revision}/comment',
447 '/{repo_name:.*?}/changeset/{revision}/comment',
426 controller='changeset', revision='tip', action='comment',
448 controller='changeset', revision='tip', action='comment',
427 conditions=dict(function=check_repo))
449 conditions=dict(function=check_repo))
428
450
429 rmap.connect('changeset_comment_delete',
451 rmap.connect('changeset_comment_delete',
430 '/{repo_name:.*?}/changeset/comment/{comment_id}/delete',
452 '/{repo_name:.*?}/changeset/comment/{comment_id}/delete',
431 controller='changeset', action='delete_comment',
453 controller='changeset', action='delete_comment',
432 conditions=dict(function=check_repo, method=["DELETE"]))
454 conditions=dict(function=check_repo, method=["DELETE"]))
433
455
434 rmap.connect('raw_changeset_home',
435 '/{repo_name:.*?}/raw-changeset/{revision}',
436 controller='changeset', action='raw_changeset',
437 revision='tip', conditions=dict(function=check_repo))
438
439 rmap.connect('changeset_info', '/changeset_info/{repo_name:.*?}/{revision}',
456 rmap.connect('changeset_info', '/changeset_info/{repo_name:.*?}/{revision}',
440 controller='changeset', action='changeset_info')
457 controller='changeset', action='changeset_info')
441
458
442 rmap.connect('compare_url',
459 rmap.connect('compare_url',
443 '/{repo_name:.*?}/compare/{org_ref_type}@{org_ref:.*?}...{other_ref_type}@{other_ref:.*?}',
460 '/{repo_name:.*?}/compare/{org_ref_type}@{org_ref:.*?}...{other_ref_type}@{other_ref:.*?}',
444 controller='compare', action='index',
461 controller='compare', action='index',
445 conditions=dict(function=check_repo),
462 conditions=dict(function=check_repo),
446 requirements=dict(
463 requirements=dict(
447 org_ref_type='(branch|book|tag|rev|org_ref_type)',
464 org_ref_type='(branch|book|tag|rev|org_ref_type)',
448 other_ref_type='(branch|book|tag|rev|other_ref_type)')
465 other_ref_type='(branch|book|tag|rev|other_ref_type)')
449 )
466 )
450
467
451 rmap.connect('pullrequest_home',
468 rmap.connect('pullrequest_home',
452 '/{repo_name:.*?}/pull-request/new', controller='pullrequests',
469 '/{repo_name:.*?}/pull-request/new', controller='pullrequests',
453 action='index', conditions=dict(function=check_repo,
470 action='index', conditions=dict(function=check_repo,
454 method=["GET"]))
471 method=["GET"]))
455
472
456 rmap.connect('pullrequest',
473 rmap.connect('pullrequest',
457 '/{repo_name:.*?}/pull-request/new', controller='pullrequests',
474 '/{repo_name:.*?}/pull-request/new', controller='pullrequests',
458 action='create', conditions=dict(function=check_repo,
475 action='create', conditions=dict(function=check_repo,
459 method=["POST"]))
476 method=["POST"]))
460
477
461 rmap.connect('pullrequest_show',
478 rmap.connect('pullrequest_show',
462 '/{repo_name:.*?}/pull-request/{pull_request_id}',
479 '/{repo_name:.*?}/pull-request/{pull_request_id}',
463 controller='pullrequests',
480 controller='pullrequests',
464 action='show', conditions=dict(function=check_repo,
481 action='show', conditions=dict(function=check_repo,
465 method=["GET"]))
482 method=["GET"]))
466 rmap.connect('pullrequest_update',
483 rmap.connect('pullrequest_update',
467 '/{repo_name:.*?}/pull-request/{pull_request_id}',
484 '/{repo_name:.*?}/pull-request/{pull_request_id}',
468 controller='pullrequests',
485 controller='pullrequests',
469 action='update', conditions=dict(function=check_repo,
486 action='update', conditions=dict(function=check_repo,
470 method=["PUT"]))
487 method=["PUT"]))
471 rmap.connect('pullrequest_delete',
488 rmap.connect('pullrequest_delete',
472 '/{repo_name:.*?}/pull-request/{pull_request_id}',
489 '/{repo_name:.*?}/pull-request/{pull_request_id}',
473 controller='pullrequests',
490 controller='pullrequests',
474 action='delete', conditions=dict(function=check_repo,
491 action='delete', conditions=dict(function=check_repo,
475 method=["DELETE"]))
492 method=["DELETE"]))
476
493
477 rmap.connect('pullrequest_show_all',
494 rmap.connect('pullrequest_show_all',
478 '/{repo_name:.*?}/pull-request',
495 '/{repo_name:.*?}/pull-request',
479 controller='pullrequests',
496 controller='pullrequests',
480 action='show_all', conditions=dict(function=check_repo,
497 action='show_all', conditions=dict(function=check_repo,
481 method=["GET"]))
498 method=["GET"]))
482
499
483 rmap.connect('pullrequest_comment',
500 rmap.connect('pullrequest_comment',
484 '/{repo_name:.*?}/pull-request-comment/{pull_request_id}',
501 '/{repo_name:.*?}/pull-request-comment/{pull_request_id}',
485 controller='pullrequests',
502 controller='pullrequests',
486 action='comment', conditions=dict(function=check_repo,
503 action='comment', conditions=dict(function=check_repo,
487 method=["POST"]))
504 method=["POST"]))
488
505
489 rmap.connect('pullrequest_comment_delete',
506 rmap.connect('pullrequest_comment_delete',
490 '/{repo_name:.*?}/pull-request-comment/{comment_id}/delete',
507 '/{repo_name:.*?}/pull-request-comment/{comment_id}/delete',
491 controller='pullrequests', action='delete_comment',
508 controller='pullrequests', action='delete_comment',
492 conditions=dict(function=check_repo, method=["DELETE"]))
509 conditions=dict(function=check_repo, method=["DELETE"]))
493
510
494 rmap.connect('summary_home', '/{repo_name:.*?}/summary',
511 rmap.connect('summary_home', '/{repo_name:.*?}/summary',
495 controller='summary', conditions=dict(function=check_repo))
512 controller='summary', conditions=dict(function=check_repo))
496
513
497 rmap.connect('shortlog_home', '/{repo_name:.*?}/shortlog',
514 rmap.connect('shortlog_home', '/{repo_name:.*?}/shortlog',
498 controller='shortlog', conditions=dict(function=check_repo))
515 controller='shortlog', conditions=dict(function=check_repo))
499
516
500 rmap.connect('branches_home', '/{repo_name:.*?}/branches',
517 rmap.connect('branches_home', '/{repo_name:.*?}/branches',
501 controller='branches', conditions=dict(function=check_repo))
518 controller='branches', conditions=dict(function=check_repo))
502
519
503 rmap.connect('tags_home', '/{repo_name:.*?}/tags',
520 rmap.connect('tags_home', '/{repo_name:.*?}/tags',
504 controller='tags', conditions=dict(function=check_repo))
521 controller='tags', conditions=dict(function=check_repo))
505
522
506 rmap.connect('bookmarks_home', '/{repo_name:.*?}/bookmarks',
523 rmap.connect('bookmarks_home', '/{repo_name:.*?}/bookmarks',
507 controller='bookmarks', conditions=dict(function=check_repo))
524 controller='bookmarks', conditions=dict(function=check_repo))
508
525
509 rmap.connect('changelog_home', '/{repo_name:.*?}/changelog',
526 rmap.connect('changelog_home', '/{repo_name:.*?}/changelog',
510 controller='changelog', conditions=dict(function=check_repo))
527 controller='changelog', conditions=dict(function=check_repo))
511
528
512 rmap.connect('changelog_details', '/{repo_name:.*?}/changelog_details/{cs}',
529 rmap.connect('changelog_details', '/{repo_name:.*?}/changelog_details/{cs}',
513 controller='changelog', action='changelog_details',
530 controller='changelog', action='changelog_details',
514 conditions=dict(function=check_repo))
531 conditions=dict(function=check_repo))
515
532
516 rmap.connect('files_home', '/{repo_name:.*?}/files/{revision}/{f_path:.*}',
533 rmap.connect('files_home', '/{repo_name:.*?}/files/{revision}/{f_path:.*}',
517 controller='files', revision='tip', f_path='',
534 controller='files', revision='tip', f_path='',
518 conditions=dict(function=check_repo))
535 conditions=dict(function=check_repo))
519
536
520 rmap.connect('files_diff_home', '/{repo_name:.*?}/diff/{f_path:.*}',
537 rmap.connect('files_diff_home', '/{repo_name:.*?}/diff/{f_path:.*}',
521 controller='files', action='diff', revision='tip', f_path='',
538 controller='files', action='diff', revision='tip', f_path='',
522 conditions=dict(function=check_repo))
539 conditions=dict(function=check_repo))
523
540
524 rmap.connect('files_rawfile_home',
541 rmap.connect('files_rawfile_home',
525 '/{repo_name:.*?}/rawfile/{revision}/{f_path:.*}',
542 '/{repo_name:.*?}/rawfile/{revision}/{f_path:.*}',
526 controller='files', action='rawfile', revision='tip',
543 controller='files', action='rawfile', revision='tip',
527 f_path='', conditions=dict(function=check_repo))
544 f_path='', conditions=dict(function=check_repo))
528
545
529 rmap.connect('files_raw_home',
546 rmap.connect('files_raw_home',
530 '/{repo_name:.*?}/raw/{revision}/{f_path:.*}',
547 '/{repo_name:.*?}/raw/{revision}/{f_path:.*}',
531 controller='files', action='raw', revision='tip', f_path='',
548 controller='files', action='raw', revision='tip', f_path='',
532 conditions=dict(function=check_repo))
549 conditions=dict(function=check_repo))
533
550
534 rmap.connect('files_annotate_home',
551 rmap.connect('files_annotate_home',
535 '/{repo_name:.*?}/annotate/{revision}/{f_path:.*}',
552 '/{repo_name:.*?}/annotate/{revision}/{f_path:.*}',
536 controller='files', action='index', revision='tip',
553 controller='files', action='index', revision='tip',
537 f_path='', annotate=True, conditions=dict(function=check_repo))
554 f_path='', annotate=True, conditions=dict(function=check_repo))
538
555
539 rmap.connect('files_edit_home',
556 rmap.connect('files_edit_home',
540 '/{repo_name:.*?}/edit/{revision}/{f_path:.*}',
557 '/{repo_name:.*?}/edit/{revision}/{f_path:.*}',
541 controller='files', action='edit', revision='tip',
558 controller='files', action='edit', revision='tip',
542 f_path='', conditions=dict(function=check_repo))
559 f_path='', conditions=dict(function=check_repo))
543
560
544 rmap.connect('files_add_home',
561 rmap.connect('files_add_home',
545 '/{repo_name:.*?}/add/{revision}/{f_path:.*}',
562 '/{repo_name:.*?}/add/{revision}/{f_path:.*}',
546 controller='files', action='add', revision='tip',
563 controller='files', action='add', revision='tip',
547 f_path='', conditions=dict(function=check_repo))
564 f_path='', conditions=dict(function=check_repo))
548
565
549 rmap.connect('files_archive_home', '/{repo_name:.*?}/archive/{fname}',
566 rmap.connect('files_archive_home', '/{repo_name:.*?}/archive/{fname}',
550 controller='files', action='archivefile',
567 controller='files', action='archivefile',
551 conditions=dict(function=check_repo))
568 conditions=dict(function=check_repo))
552
569
553 rmap.connect('files_nodelist_home',
570 rmap.connect('files_nodelist_home',
554 '/{repo_name:.*?}/nodelist/{revision}/{f_path:.*}',
571 '/{repo_name:.*?}/nodelist/{revision}/{f_path:.*}',
555 controller='files', action='nodelist',
572 controller='files', action='nodelist',
556 conditions=dict(function=check_repo))
573 conditions=dict(function=check_repo))
557
574
558 rmap.connect('repo_settings_delete', '/{repo_name:.*?}/settings',
575 rmap.connect('repo_settings_delete', '/{repo_name:.*?}/settings',
559 controller='settings', action="delete",
576 controller='settings', action="delete",
560 conditions=dict(method=["DELETE"], function=check_repo))
577 conditions=dict(method=["DELETE"], function=check_repo))
561
578
562 rmap.connect('repo_settings_update', '/{repo_name:.*?}/settings',
579 rmap.connect('repo_settings_update', '/{repo_name:.*?}/settings',
563 controller='settings', action="update",
580 controller='settings', action="update",
564 conditions=dict(method=["PUT"], function=check_repo))
581 conditions=dict(method=["PUT"], function=check_repo))
565
582
566 rmap.connect('repo_settings_home', '/{repo_name:.*?}/settings',
583 rmap.connect('repo_settings_home', '/{repo_name:.*?}/settings',
567 controller='settings', action='index',
584 controller='settings', action='index',
568 conditions=dict(function=check_repo))
585 conditions=dict(function=check_repo))
569
586
570 rmap.connect('toggle_locking', "/{repo_name:.*?}/locking_toggle",
587 rmap.connect('toggle_locking', "/{repo_name:.*?}/locking_toggle",
571 controller='settings', action="toggle_locking",
588 controller='settings', action="toggle_locking",
572 conditions=dict(method=["GET"], function=check_repo))
589 conditions=dict(method=["GET"], function=check_repo))
573
590
574 rmap.connect('repo_fork_create_home', '/{repo_name:.*?}/fork',
591 rmap.connect('repo_fork_create_home', '/{repo_name:.*?}/fork',
575 controller='forks', action='fork_create',
592 controller='forks', action='fork_create',
576 conditions=dict(function=check_repo, method=["POST"]))
593 conditions=dict(function=check_repo, method=["POST"]))
577
594
578 rmap.connect('repo_fork_home', '/{repo_name:.*?}/fork',
595 rmap.connect('repo_fork_home', '/{repo_name:.*?}/fork',
579 controller='forks', action='fork',
596 controller='forks', action='fork',
580 conditions=dict(function=check_repo))
597 conditions=dict(function=check_repo))
581
598
582 rmap.connect('repo_forks_home', '/{repo_name:.*?}/forks',
599 rmap.connect('repo_forks_home', '/{repo_name:.*?}/forks',
583 controller='forks', action='forks',
600 controller='forks', action='forks',
584 conditions=dict(function=check_repo))
601 conditions=dict(function=check_repo))
585
602
586 rmap.connect('repo_followers_home', '/{repo_name:.*?}/followers',
603 rmap.connect('repo_followers_home', '/{repo_name:.*?}/followers',
587 controller='followers', action='followers',
604 controller='followers', action='followers',
588 conditions=dict(function=check_repo))
605 conditions=dict(function=check_repo))
589
606
590 return rmap
607 return rmap
@@ -1,381 +1,391 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.changeset
3 rhodecode.controllers.changeset
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 changeset controller for pylons showoing changes beetween
6 changeset controller for pylons showoing changes beetween
7 revisions
7 revisions
8
8
9 :created_on: Apr 25, 2010
9 :created_on: Apr 25, 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 logging
26 import logging
27 import traceback
27 import traceback
28 from collections import defaultdict
28 from collections import defaultdict
29 from webob.exc import HTTPForbidden, HTTPBadRequest
29 from webob.exc import HTTPForbidden, HTTPBadRequest
30
30
31 from pylons import tmpl_context as c, url, request, response
31 from pylons import tmpl_context as c, url, request, response
32 from pylons.i18n.translation import _
32 from pylons.i18n.translation import _
33 from pylons.controllers.util import redirect
33 from pylons.controllers.util import redirect
34 from pylons.decorators import jsonify
34 from pylons.decorators import jsonify
35
35
36 from rhodecode.lib.vcs.exceptions import RepositoryError, ChangesetError, \
36 from rhodecode.lib.vcs.exceptions import RepositoryError, ChangesetError, \
37 ChangesetDoesNotExistError
37 ChangesetDoesNotExistError
38 from rhodecode.lib.vcs.nodes import FileNode
38 from rhodecode.lib.vcs.nodes import FileNode
39
39
40 import rhodecode.lib.helpers as h
40 import rhodecode.lib.helpers as h
41 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
41 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
42 from rhodecode.lib.base import BaseRepoController, render
42 from rhodecode.lib.base import BaseRepoController, render
43 from rhodecode.lib.utils import action_logger
43 from rhodecode.lib.utils import action_logger
44 from rhodecode.lib.compat import OrderedDict
44 from rhodecode.lib.compat import OrderedDict
45 from rhodecode.lib import diffs
45 from rhodecode.lib import diffs
46 from rhodecode.model.db import ChangesetComment, ChangesetStatus
46 from rhodecode.model.db import ChangesetComment, ChangesetStatus
47 from rhodecode.model.comment import ChangesetCommentsModel
47 from rhodecode.model.comment import ChangesetCommentsModel
48 from rhodecode.model.changeset_status import ChangesetStatusModel
48 from rhodecode.model.changeset_status import ChangesetStatusModel
49 from rhodecode.model.meta import Session
49 from rhodecode.model.meta import Session
50 from rhodecode.lib.diffs import LimitedDiffContainer
50 from rhodecode.lib.diffs import LimitedDiffContainer
51 from rhodecode.model.repo import RepoModel
51 from rhodecode.model.repo import RepoModel
52 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError
52 from rhodecode.lib.exceptions import StatusChangeOnClosedPullRequestError
53 from rhodecode.lib.vcs.backends.base import EmptyChangeset
53 from rhodecode.lib.vcs.backends.base import EmptyChangeset
54 from rhodecode.lib.utils2 import safe_unicode
54
55
55 log = logging.getLogger(__name__)
56 log = logging.getLogger(__name__)
56
57
57
58
58 def _update_with_GET(params, GET):
59 def _update_with_GET(params, GET):
59 for k in ['diff1', 'diff2', 'diff']:
60 for k in ['diff1', 'diff2', 'diff']:
60 params[k] += GET.getall(k)
61 params[k] += GET.getall(k)
61
62
62
63
63 def anchor_url(revision, path, GET):
64 def anchor_url(revision, path, GET):
64 fid = h.FID(revision, path)
65 fid = h.FID(revision, path)
65 return h.url.current(anchor=fid, **dict(GET))
66 return h.url.current(anchor=fid, **dict(GET))
66
67
67
68
68 def get_ignore_ws(fid, GET):
69 def get_ignore_ws(fid, GET):
69 ig_ws_global = GET.get('ignorews')
70 ig_ws_global = GET.get('ignorews')
70 ig_ws = filter(lambda k: k.startswith('WS'), GET.getall(fid))
71 ig_ws = filter(lambda k: k.startswith('WS'), GET.getall(fid))
71 if ig_ws:
72 if ig_ws:
72 try:
73 try:
73 return int(ig_ws[0].split(':')[-1])
74 return int(ig_ws[0].split(':')[-1])
74 except:
75 except:
75 pass
76 pass
76 return ig_ws_global
77 return ig_ws_global
77
78
78
79
79 def _ignorews_url(GET, fileid=None):
80 def _ignorews_url(GET, fileid=None):
80 fileid = str(fileid) if fileid else None
81 fileid = str(fileid) if fileid else None
81 params = defaultdict(list)
82 params = defaultdict(list)
82 _update_with_GET(params, GET)
83 _update_with_GET(params, GET)
83 lbl = _('show white space')
84 lbl = _('show white space')
84 ig_ws = get_ignore_ws(fileid, GET)
85 ig_ws = get_ignore_ws(fileid, GET)
85 ln_ctx = get_line_ctx(fileid, GET)
86 ln_ctx = get_line_ctx(fileid, GET)
86 # global option
87 # global option
87 if fileid is None:
88 if fileid is None:
88 if ig_ws is None:
89 if ig_ws is None:
89 params['ignorews'] += [1]
90 params['ignorews'] += [1]
90 lbl = _('ignore white space')
91 lbl = _('ignore white space')
91 ctx_key = 'context'
92 ctx_key = 'context'
92 ctx_val = ln_ctx
93 ctx_val = ln_ctx
93 # per file options
94 # per file options
94 else:
95 else:
95 if ig_ws is None:
96 if ig_ws is None:
96 params[fileid] += ['WS:1']
97 params[fileid] += ['WS:1']
97 lbl = _('ignore white space')
98 lbl = _('ignore white space')
98
99
99 ctx_key = fileid
100 ctx_key = fileid
100 ctx_val = 'C:%s' % ln_ctx
101 ctx_val = 'C:%s' % ln_ctx
101 # if we have passed in ln_ctx pass it along to our params
102 # if we have passed in ln_ctx pass it along to our params
102 if ln_ctx:
103 if ln_ctx:
103 params[ctx_key] += [ctx_val]
104 params[ctx_key] += [ctx_val]
104
105
105 params['anchor'] = fileid
106 params['anchor'] = fileid
106 img = h.image(h.url('/images/icons/text_strikethrough.png'), lbl, class_='icon')
107 img = h.image(h.url('/images/icons/text_strikethrough.png'), lbl, class_='icon')
107 return h.link_to(img, h.url.current(**params), title=lbl, class_='tooltip')
108 return h.link_to(img, h.url.current(**params), title=lbl, class_='tooltip')
108
109
109
110
110 def get_line_ctx(fid, GET):
111 def get_line_ctx(fid, GET):
111 ln_ctx_global = GET.get('context')
112 ln_ctx_global = GET.get('context')
112 if fid:
113 if fid:
113 ln_ctx = filter(lambda k: k.startswith('C'), GET.getall(fid))
114 ln_ctx = filter(lambda k: k.startswith('C'), GET.getall(fid))
114 else:
115 else:
115 _ln_ctx = filter(lambda k: k.startswith('C'), GET)
116 _ln_ctx = filter(lambda k: k.startswith('C'), GET)
116 ln_ctx = GET.get(_ln_ctx[0]) if _ln_ctx else ln_ctx_global
117 ln_ctx = GET.get(_ln_ctx[0]) if _ln_ctx else ln_ctx_global
117 if ln_ctx:
118 if ln_ctx:
118 ln_ctx = [ln_ctx]
119 ln_ctx = [ln_ctx]
119
120
120 if ln_ctx:
121 if ln_ctx:
121 retval = ln_ctx[0].split(':')[-1]
122 retval = ln_ctx[0].split(':')[-1]
122 else:
123 else:
123 retval = ln_ctx_global
124 retval = ln_ctx_global
124
125
125 try:
126 try:
126 return int(retval)
127 return int(retval)
127 except:
128 except:
128 return 3
129 return 3
129
130
130
131
131 def _context_url(GET, fileid=None):
132 def _context_url(GET, fileid=None):
132 """
133 """
133 Generates url for context lines
134 Generates url for context lines
134
135
135 :param fileid:
136 :param fileid:
136 """
137 """
137
138
138 fileid = str(fileid) if fileid else None
139 fileid = str(fileid) if fileid else None
139 ig_ws = get_ignore_ws(fileid, GET)
140 ig_ws = get_ignore_ws(fileid, GET)
140 ln_ctx = (get_line_ctx(fileid, GET) or 3) * 2
141 ln_ctx = (get_line_ctx(fileid, GET) or 3) * 2
141
142
142 params = defaultdict(list)
143 params = defaultdict(list)
143 _update_with_GET(params, GET)
144 _update_with_GET(params, GET)
144
145
145 # global option
146 # global option
146 if fileid is None:
147 if fileid is None:
147 if ln_ctx > 0:
148 if ln_ctx > 0:
148 params['context'] += [ln_ctx]
149 params['context'] += [ln_ctx]
149
150
150 if ig_ws:
151 if ig_ws:
151 ig_ws_key = 'ignorews'
152 ig_ws_key = 'ignorews'
152 ig_ws_val = 1
153 ig_ws_val = 1
153
154
154 # per file option
155 # per file option
155 else:
156 else:
156 params[fileid] += ['C:%s' % ln_ctx]
157 params[fileid] += ['C:%s' % ln_ctx]
157 ig_ws_key = fileid
158 ig_ws_key = fileid
158 ig_ws_val = 'WS:%s' % 1
159 ig_ws_val = 'WS:%s' % 1
159
160
160 if ig_ws:
161 if ig_ws:
161 params[ig_ws_key] += [ig_ws_val]
162 params[ig_ws_key] += [ig_ws_val]
162
163
163 lbl = _('%s line context') % ln_ctx
164 lbl = _('%s line context') % ln_ctx
164
165
165 params['anchor'] = fileid
166 params['anchor'] = fileid
166 img = h.image(h.url('/images/icons/table_add.png'), lbl, class_='icon')
167 img = h.image(h.url('/images/icons/table_add.png'), lbl, class_='icon')
167 return h.link_to(img, h.url.current(**params), title=lbl, class_='tooltip')
168 return h.link_to(img, h.url.current(**params), title=lbl, class_='tooltip')
168
169
169
170
170 class ChangesetController(BaseRepoController):
171 class ChangesetController(BaseRepoController):
171
172
172 @LoginRequired()
173 @LoginRequired()
173 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
174 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
174 'repository.admin')
175 'repository.admin')
175 def __before__(self):
176 def __before__(self):
176 super(ChangesetController, self).__before__()
177 super(ChangesetController, self).__before__()
177 c.affected_files_cut_off = 60
178 c.affected_files_cut_off = 60
178 repo_model = RepoModel()
179 repo_model = RepoModel()
179 c.users_array = repo_model.get_users_js()
180 c.users_array = repo_model.get_users_js()
180 c.users_groups_array = repo_model.get_users_groups_js()
181 c.users_groups_array = repo_model.get_users_groups_js()
181
182
182 def index(self, revision):
183 def index(self, revision, method='show'):
183 method = request.GET.get('diff', 'show')
184 c.anchor_url = anchor_url
184 c.anchor_url = anchor_url
185 c.ignorews_url = _ignorews_url
185 c.ignorews_url = _ignorews_url
186 c.context_url = _context_url
186 c.context_url = _context_url
187 limit_off = request.GET.get('fulldiff')
187 c.fulldiff = fulldiff = request.GET.get('fulldiff')
188 #get ranges of revisions if preset
188 #get ranges of revisions if preset
189 rev_range = revision.split('...')[:2]
189 rev_range = revision.split('...')[:2]
190 enable_comments = True
190 enable_comments = True
191 try:
191 try:
192 if len(rev_range) == 2:
192 if len(rev_range) == 2:
193 enable_comments = False
193 enable_comments = False
194 rev_start = rev_range[0]
194 rev_start = rev_range[0]
195 rev_end = rev_range[1]
195 rev_end = rev_range[1]
196 rev_ranges = c.rhodecode_repo.get_changesets(start=rev_start,
196 rev_ranges = c.rhodecode_repo.get_changesets(start=rev_start,
197 end=rev_end)
197 end=rev_end)
198 else:
198 else:
199 rev_ranges = [c.rhodecode_repo.get_changeset(revision)]
199 rev_ranges = [c.rhodecode_repo.get_changeset(revision)]
200
200
201 c.cs_ranges = list(rev_ranges)
201 c.cs_ranges = list(rev_ranges)
202 if not c.cs_ranges:
202 if not c.cs_ranges:
203 raise RepositoryError('Changeset range returned empty result')
203 raise RepositoryError('Changeset range returned empty result')
204
204
205 except (RepositoryError, ChangesetDoesNotExistError, Exception), e:
205 except (RepositoryError, ChangesetDoesNotExistError, Exception), e:
206 log.error(traceback.format_exc())
206 log.error(traceback.format_exc())
207 h.flash(str(e), category='warning')
207 h.flash(str(e), category='warning')
208 return redirect(url('home'))
208 return redirect(url('home'))
209
209
210 c.changes = OrderedDict()
210 c.changes = OrderedDict()
211
211
212 c.lines_added = 0 # count of lines added
212 c.lines_added = 0 # count of lines added
213 c.lines_deleted = 0 # count of lines removes
213 c.lines_deleted = 0 # count of lines removes
214
214
215 c.changeset_statuses = ChangesetStatus.STATUSES
215 c.changeset_statuses = ChangesetStatus.STATUSES
216 c.comments = []
216 c.comments = []
217 c.statuses = []
217 c.statuses = []
218 c.inline_comments = []
218 c.inline_comments = []
219 c.inline_cnt = 0
219 c.inline_cnt = 0
220
220
221 # Iterate over ranges (default changeset view is always one changeset)
221 # Iterate over ranges (default changeset view is always one changeset)
222 for changeset in c.cs_ranges:
222 for changeset in c.cs_ranges:
223 inlines = []
223 inlines = []
224 if method == 'show':
224 if method == 'show':
225 c.statuses.extend([ChangesetStatusModel()\
225 c.statuses.extend([ChangesetStatusModel()\
226 .get_status(c.rhodecode_db_repo.repo_id,
226 .get_status(c.rhodecode_db_repo.repo_id,
227 changeset.raw_id)])
227 changeset.raw_id)])
228
228
229 c.comments.extend(ChangesetCommentsModel()\
229 c.comments.extend(ChangesetCommentsModel()\
230 .get_comments(c.rhodecode_db_repo.repo_id,
230 .get_comments(c.rhodecode_db_repo.repo_id,
231 revision=changeset.raw_id))
231 revision=changeset.raw_id))
232 inlines = ChangesetCommentsModel()\
232 inlines = ChangesetCommentsModel()\
233 .get_inline_comments(c.rhodecode_db_repo.repo_id,
233 .get_inline_comments(c.rhodecode_db_repo.repo_id,
234 revision=changeset.raw_id)
234 revision=changeset.raw_id)
235 c.inline_comments.extend(inlines)
235 c.inline_comments.extend(inlines)
236
236
237 c.changes[changeset.raw_id] = []
237 c.changes[changeset.raw_id] = []
238
238
239 cs2 = changeset.raw_id
239 cs2 = changeset.raw_id
240 cs1 = changeset.parents[0].raw_id if changeset.parents else EmptyChangeset()
240 cs1 = changeset.parents[0].raw_id if changeset.parents else EmptyChangeset()
241 context_lcl = get_line_ctx('', request.GET)
241 context_lcl = get_line_ctx('', request.GET)
242 ign_whitespace_lcl = ign_whitespace_lcl = get_ignore_ws('', request.GET)
242 ign_whitespace_lcl = ign_whitespace_lcl = get_ignore_ws('', request.GET)
243
243
244 _diff = c.rhodecode_repo.get_diff(cs1, cs2,
244 _diff = c.rhodecode_repo.get_diff(cs1, cs2,
245 ignore_whitespace=ign_whitespace_lcl, context=context_lcl)
245 ignore_whitespace=ign_whitespace_lcl, context=context_lcl)
246 diff_limit = self.cut_off_limit if not limit_off else None
246 diff_limit = self.cut_off_limit if not fulldiff else None
247 diff_processor = diffs.DiffProcessor(_diff,
247 diff_processor = diffs.DiffProcessor(_diff,
248 vcs=c.rhodecode_repo.alias,
248 vcs=c.rhodecode_repo.alias,
249 format='gitdiff',
249 format='gitdiff',
250 diff_limit=diff_limit)
250 diff_limit=diff_limit)
251 cs_changes = OrderedDict()
251 cs_changes = OrderedDict()
252 if method == 'show':
252 if method == 'show':
253 _parsed = diff_processor.prepare()
253 _parsed = diff_processor.prepare()
254 c.limited_diff = False
254 c.limited_diff = False
255 if isinstance(_parsed, LimitedDiffContainer):
255 if isinstance(_parsed, LimitedDiffContainer):
256 c.limited_diff = True
256 c.limited_diff = True
257 for f in _parsed:
257 for f in _parsed:
258 st = f['stats']
258 st = f['stats']
259 if st[0] != 'b':
259 if st[0] != 'b':
260 c.lines_added += st[0]
260 c.lines_added += st[0]
261 c.lines_deleted += st[1]
261 c.lines_deleted += st[1]
262 fid = h.FID(changeset.raw_id, f['filename'])
262 fid = h.FID(changeset.raw_id, f['filename'])
263 diff = diff_processor.as_html(enable_comments=enable_comments,
263 diff = diff_processor.as_html(enable_comments=enable_comments,
264 parsed_lines=[f])
264 parsed_lines=[f])
265 cs_changes[fid] = [cs1, cs2, f['operation'], f['filename'],
265 cs_changes[fid] = [cs1, cs2, f['operation'], f['filename'],
266 diff, st]
266 diff, st]
267 else:
267 else:
268 # downloads/raw we only need RAW diff nothing else
268 # downloads/raw we only need RAW diff nothing else
269 diff = diff_processor.as_raw()
269 diff = diff_processor.as_raw()
270 cs_changes[''] = [None, None, None, None, diff, None]
270 cs_changes[''] = [None, None, None, None, diff, None]
271 c.changes[changeset.raw_id] = cs_changes
271 c.changes[changeset.raw_id] = cs_changes
272
272
273 # count inline comments
273 # count inline comments
274 for __, lines in c.inline_comments:
274 for __, lines in c.inline_comments:
275 for comments in lines.values():
275 for comments in lines.values():
276 c.inline_cnt += len(comments)
276 c.inline_cnt += len(comments)
277
277
278 if len(c.cs_ranges) == 1:
278 if len(c.cs_ranges) == 1:
279 c.changeset = c.cs_ranges[0]
279 c.changeset = c.cs_ranges[0]
280 c.parent_tmpl = ''.join(['# Parent %s\n' % x.raw_id
280 c.parent_tmpl = ''.join(['# Parent %s\n' % x.raw_id
281 for x in c.changeset.parents])
281 for x in c.changeset.parents])
282 if method == 'download':
282 if method == 'download':
283 response.content_type = 'text/plain'
283 response.content_type = 'text/plain'
284 response.content_disposition = 'attachment; filename=%s.patch' \
284 response.content_disposition = 'attachment; filename=%s.diff' \
285 % revision
285 % revision[:12]
286 return render('changeset/raw_changeset.html')
286 return diff
287 elif method == 'patch':
288 response.content_type = 'text/plain'
289 c.diff = safe_unicode(diff)
290 return render('changeset/patch_changeset.html')
287 elif method == 'raw':
291 elif method == 'raw':
288 response.content_type = 'text/plain'
292 response.content_type = 'text/plain'
289 return render('changeset/raw_changeset.html')
293 return diff
290 elif method == 'show':
294 elif method == 'show':
291 if len(c.cs_ranges) == 1:
295 if len(c.cs_ranges) == 1:
292 return render('changeset/changeset.html')
296 return render('changeset/changeset.html')
293 else:
297 else:
294 return render('changeset/changeset_range.html')
298 return render('changeset/changeset_range.html')
295
299
296 def raw_changeset(self, revision):
300 def changeset_raw(self, revision):
297 return self.index(revision)
301 return self.index(revision, method='raw')
302
303 def changeset_patch(self, revision):
304 return self.index(revision, method='patch')
305
306 def changeset_download(self, revision):
307 return self.index(revision, method='download')
298
308
299 @jsonify
309 @jsonify
300 def comment(self, repo_name, revision):
310 def comment(self, repo_name, revision):
301 status = request.POST.get('changeset_status')
311 status = request.POST.get('changeset_status')
302 change_status = request.POST.get('change_changeset_status')
312 change_status = request.POST.get('change_changeset_status')
303 text = request.POST.get('text')
313 text = request.POST.get('text')
304 if status and change_status:
314 if status and change_status:
305 text = text or (_('Status change -> %s')
315 text = text or (_('Status change -> %s')
306 % ChangesetStatus.get_status_lbl(status))
316 % ChangesetStatus.get_status_lbl(status))
307
317
308 comm = ChangesetCommentsModel().create(
318 comm = ChangesetCommentsModel().create(
309 text=text,
319 text=text,
310 repo=c.rhodecode_db_repo.repo_id,
320 repo=c.rhodecode_db_repo.repo_id,
311 user=c.rhodecode_user.user_id,
321 user=c.rhodecode_user.user_id,
312 revision=revision,
322 revision=revision,
313 f_path=request.POST.get('f_path'),
323 f_path=request.POST.get('f_path'),
314 line_no=request.POST.get('line'),
324 line_no=request.POST.get('line'),
315 status_change=(ChangesetStatus.get_status_lbl(status)
325 status_change=(ChangesetStatus.get_status_lbl(status)
316 if status and change_status else None)
326 if status and change_status else None)
317 )
327 )
318
328
319 # get status if set !
329 # get status if set !
320 if status and change_status:
330 if status and change_status:
321 # if latest status was from pull request and it's closed
331 # if latest status was from pull request and it's closed
322 # disallow changing status !
332 # disallow changing status !
323 # dont_allow_on_closed_pull_request = True !
333 # dont_allow_on_closed_pull_request = True !
324
334
325 try:
335 try:
326 ChangesetStatusModel().set_status(
336 ChangesetStatusModel().set_status(
327 c.rhodecode_db_repo.repo_id,
337 c.rhodecode_db_repo.repo_id,
328 status,
338 status,
329 c.rhodecode_user.user_id,
339 c.rhodecode_user.user_id,
330 comm,
340 comm,
331 revision=revision,
341 revision=revision,
332 dont_allow_on_closed_pull_request=True
342 dont_allow_on_closed_pull_request=True
333 )
343 )
334 except StatusChangeOnClosedPullRequestError:
344 except StatusChangeOnClosedPullRequestError:
335 log.error(traceback.format_exc())
345 log.error(traceback.format_exc())
336 msg = _('Changing status on a changeset associated with'
346 msg = _('Changing status on a changeset associated with'
337 'a closed pull request is not allowed')
347 'a closed pull request is not allowed')
338 h.flash(msg, category='warning')
348 h.flash(msg, category='warning')
339 return redirect(h.url('changeset_home', repo_name=repo_name,
349 return redirect(h.url('changeset_home', repo_name=repo_name,
340 revision=revision))
350 revision=revision))
341 action_logger(self.rhodecode_user,
351 action_logger(self.rhodecode_user,
342 'user_commented_revision:%s' % revision,
352 'user_commented_revision:%s' % revision,
343 c.rhodecode_db_repo, self.ip_addr, self.sa)
353 c.rhodecode_db_repo, self.ip_addr, self.sa)
344
354
345 Session().commit()
355 Session().commit()
346
356
347 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
357 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
348 return redirect(h.url('changeset_home', repo_name=repo_name,
358 return redirect(h.url('changeset_home', repo_name=repo_name,
349 revision=revision))
359 revision=revision))
350
360
351 data = {
361 data = {
352 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
362 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
353 }
363 }
354 if comm:
364 if comm:
355 c.co = comm
365 c.co = comm
356 data.update(comm.get_dict())
366 data.update(comm.get_dict())
357 data.update({'rendered_text':
367 data.update({'rendered_text':
358 render('changeset/changeset_comment_block.html')})
368 render('changeset/changeset_comment_block.html')})
359
369
360 return data
370 return data
361
371
362 @jsonify
372 @jsonify
363 def delete_comment(self, repo_name, comment_id):
373 def delete_comment(self, repo_name, comment_id):
364 co = ChangesetComment.get(comment_id)
374 co = ChangesetComment.get(comment_id)
365 owner = lambda: co.author.user_id == c.rhodecode_user.user_id
375 owner = lambda: co.author.user_id == c.rhodecode_user.user_id
366 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
376 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
367 ChangesetCommentsModel().delete(comment=co)
377 ChangesetCommentsModel().delete(comment=co)
368 Session().commit()
378 Session().commit()
369 return True
379 return True
370 else:
380 else:
371 raise HTTPForbidden()
381 raise HTTPForbidden()
372
382
373 @jsonify
383 @jsonify
374 def changeset_info(self, repo_name, revision):
384 def changeset_info(self, repo_name, revision):
375 if request.is_xhr:
385 if request.is_xhr:
376 try:
386 try:
377 return c.rhodecode_repo.get_changeset(revision)
387 return c.rhodecode_repo.get_changeset(revision)
378 except ChangesetDoesNotExistError, e:
388 except ChangesetDoesNotExistError, e:
379 return EmptyChangeset(message=str(e))
389 return EmptyChangeset(message=str(e))
380 else:
390 else:
381 raise HTTPBadRequest()
391 raise HTTPBadRequest()
@@ -1,766 +1,767 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 logging
30 import logging
31 import traceback
31 import traceback
32
32
33 from itertools import tee, imap
33 from itertools import tee, imap
34
34
35 from mercurial import patch
35 from mercurial import patch
36 from mercurial.mdiff import diffopts
36 from mercurial.mdiff import diffopts
37 from mercurial.bundlerepo import bundlerepository
37 from mercurial.bundlerepo import bundlerepository
38
38
39 from pylons.i18n.translation import _
39 from pylons.i18n.translation import _
40
40
41 from rhodecode.lib.compat import BytesIO
41 from rhodecode.lib.compat import BytesIO
42 from rhodecode.lib.vcs.utils.hgcompat import localrepo
42 from rhodecode.lib.vcs.utils.hgcompat import localrepo
43 from rhodecode.lib.vcs.exceptions import VCSError
43 from rhodecode.lib.vcs.exceptions import VCSError
44 from rhodecode.lib.vcs.nodes import FileNode, SubModuleNode
44 from rhodecode.lib.vcs.nodes import FileNode, SubModuleNode
45 from rhodecode.lib.vcs.backends.base import EmptyChangeset
45 from rhodecode.lib.vcs.backends.base import EmptyChangeset
46 from rhodecode.lib.helpers import escape
46 from rhodecode.lib.helpers import escape
47 from rhodecode.lib.utils import make_ui
47 from rhodecode.lib.utils import make_ui
48 from rhodecode.lib.utils2 import safe_unicode
48 from rhodecode.lib.utils2 import safe_unicode
49
49
50 log = logging.getLogger(__name__)
50 log = logging.getLogger(__name__)
51
51
52
52
53 def wrap_to_table(str_):
53 def wrap_to_table(str_):
54 return '''<table class="code-difftable">
54 return '''<table class="code-difftable">
55 <tr class="line no-comment">
55 <tr class="line no-comment">
56 <td class="lineno new"></td>
56 <td class="lineno new"></td>
57 <td class="code no-comment"><pre>%s</pre></td>
57 <td class="code no-comment"><pre>%s</pre></td>
58 </tr>
58 </tr>
59 </table>''' % str_
59 </table>''' % str_
60
60
61
61
62 def wrapped_diff(filenode_old, filenode_new, cut_off_limit=None,
62 def wrapped_diff(filenode_old, filenode_new, cut_off_limit=None,
63 ignore_whitespace=True, line_context=3,
63 ignore_whitespace=True, line_context=3,
64 enable_comments=False):
64 enable_comments=False):
65 """
65 """
66 returns a wrapped diff into a table, checks for cut_off_limit and presents
66 returns a wrapped diff into a table, checks for cut_off_limit and presents
67 proper message
67 proper message
68 """
68 """
69
69
70 if filenode_old is None:
70 if filenode_old is None:
71 filenode_old = FileNode(filenode_new.path, '', EmptyChangeset())
71 filenode_old = FileNode(filenode_new.path, '', EmptyChangeset())
72
72
73 if filenode_old.is_binary or filenode_new.is_binary:
73 if filenode_old.is_binary or filenode_new.is_binary:
74 diff = wrap_to_table(_('binary file'))
74 diff = wrap_to_table(_('binary file'))
75 stats = (0, 0)
75 stats = (0, 0)
76 size = 0
76 size = 0
77
77
78 elif cut_off_limit != -1 and (cut_off_limit is None or
78 elif cut_off_limit != -1 and (cut_off_limit is None or
79 (filenode_old.size < cut_off_limit and filenode_new.size < cut_off_limit)):
79 (filenode_old.size < cut_off_limit and filenode_new.size < cut_off_limit)):
80
80
81 f_gitdiff = get_gitdiff(filenode_old, filenode_new,
81 f_gitdiff = get_gitdiff(filenode_old, filenode_new,
82 ignore_whitespace=ignore_whitespace,
82 ignore_whitespace=ignore_whitespace,
83 context=line_context)
83 context=line_context)
84 diff_processor = DiffProcessor(f_gitdiff, format='gitdiff')
84 diff_processor = DiffProcessor(f_gitdiff, format='gitdiff')
85
85
86 diff = diff_processor.as_html(enable_comments=enable_comments)
86 diff = diff_processor.as_html(enable_comments=enable_comments)
87 stats = diff_processor.stat()
87 stats = diff_processor.stat()
88 size = len(diff or '')
88 size = len(diff or '')
89 else:
89 else:
90 diff = wrap_to_table(_('Changeset was too big and was cut off, use '
90 diff = wrap_to_table(_('Changeset was too big and was cut off, use '
91 'diff menu to display this diff'))
91 'diff menu to display this diff'))
92 stats = (0, 0)
92 stats = (0, 0)
93 size = 0
93 size = 0
94 if not diff:
94 if not diff:
95 submodules = filter(lambda o: isinstance(o, SubModuleNode),
95 submodules = filter(lambda o: isinstance(o, SubModuleNode),
96 [filenode_new, filenode_old])
96 [filenode_new, filenode_old])
97 if submodules:
97 if submodules:
98 diff = wrap_to_table(escape('Submodule %r' % submodules[0]))
98 diff = wrap_to_table(escape('Submodule %r' % submodules[0]))
99 else:
99 else:
100 diff = wrap_to_table(_('No changes detected'))
100 diff = wrap_to_table(_('No changes detected'))
101
101
102 cs1 = filenode_old.changeset.raw_id
102 cs1 = filenode_old.changeset.raw_id
103 cs2 = filenode_new.changeset.raw_id
103 cs2 = filenode_new.changeset.raw_id
104
104
105 return size, cs1, cs2, diff, stats
105 return size, cs1, cs2, diff, stats
106
106
107
107
108 def get_gitdiff(filenode_old, filenode_new, ignore_whitespace=True, context=3):
108 def get_gitdiff(filenode_old, filenode_new, ignore_whitespace=True, context=3):
109 """
109 """
110 Returns git style diff between given ``filenode_old`` and ``filenode_new``.
110 Returns git style diff between given ``filenode_old`` and ``filenode_new``.
111
111
112 :param ignore_whitespace: ignore whitespaces in diff
112 :param ignore_whitespace: ignore whitespaces in diff
113 """
113 """
114 # make sure we pass in default context
114 # make sure we pass in default context
115 context = context or 3
115 context = context or 3
116 submodules = filter(lambda o: isinstance(o, SubModuleNode),
116 submodules = filter(lambda o: isinstance(o, SubModuleNode),
117 [filenode_new, filenode_old])
117 [filenode_new, filenode_old])
118 if submodules:
118 if submodules:
119 return ''
119 return ''
120
120
121 for filenode in (filenode_old, filenode_new):
121 for filenode in (filenode_old, filenode_new):
122 if not isinstance(filenode, FileNode):
122 if not isinstance(filenode, FileNode):
123 raise VCSError("Given object should be FileNode object, not %s"
123 raise VCSError("Given object should be FileNode object, not %s"
124 % filenode.__class__)
124 % filenode.__class__)
125
125
126 repo = filenode_new.changeset.repository
126 repo = filenode_new.changeset.repository
127 old_raw_id = getattr(filenode_old.changeset, 'raw_id', repo.EMPTY_CHANGESET)
127 old_raw_id = getattr(filenode_old.changeset, 'raw_id', repo.EMPTY_CHANGESET)
128 new_raw_id = getattr(filenode_new.changeset, 'raw_id', repo.EMPTY_CHANGESET)
128 new_raw_id = getattr(filenode_new.changeset, 'raw_id', repo.EMPTY_CHANGESET)
129
129
130 vcs_gitdiff = repo.get_diff(old_raw_id, new_raw_id, filenode_new.path,
130 vcs_gitdiff = repo.get_diff(old_raw_id, new_raw_id, filenode_new.path,
131 ignore_whitespace, context)
131 ignore_whitespace, context)
132 return vcs_gitdiff
132 return vcs_gitdiff
133
133
134 NEW_FILENODE = 1
134 NEW_FILENODE = 1
135 DEL_FILENODE = 2
135 DEL_FILENODE = 2
136 MOD_FILENODE = 3
136 MOD_FILENODE = 3
137 RENAMED_FILENODE = 4
137 RENAMED_FILENODE = 4
138 CHMOD_FILENODE = 5
138 CHMOD_FILENODE = 5
139
139
140
140
141 class DiffLimitExceeded(Exception):
141 class DiffLimitExceeded(Exception):
142 pass
142 pass
143
143
144
144
145 class LimitedDiffContainer(object):
145 class LimitedDiffContainer(object):
146
146
147 def __init__(self, diff_limit, cur_diff_size, diff):
147 def __init__(self, diff_limit, cur_diff_size, diff):
148 self.diff = diff
148 self.diff = diff
149 self.diff_limit = diff_limit
149 self.diff_limit = diff_limit
150 self.cur_diff_size = cur_diff_size
150 self.cur_diff_size = cur_diff_size
151
151
152 def __iter__(self):
152 def __iter__(self):
153 for l in self.diff:
153 for l in self.diff:
154 yield l
154 yield l
155
155
156
156
157 class DiffProcessor(object):
157 class DiffProcessor(object):
158 """
158 """
159 Give it a unified or git diff and it returns a list of the files that were
159 Give it a unified or git diff and it returns a list of the files that were
160 mentioned in the diff together with a dict of meta information that
160 mentioned in the diff together with a dict of meta information that
161 can be used to render it in a HTML template.
161 can be used to render it in a HTML template.
162 """
162 """
163 _chunk_re = re.compile(r'@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@(.*)')
163 _chunk_re = re.compile(r'@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@(.*)')
164 _newline_marker = '\\ No newline at end of file\n'
164 _newline_marker = '\\ No newline at end of file\n'
165 _git_header_re = re.compile(r"""
165 _git_header_re = re.compile(r"""
166 #^diff[ ]--git
166 #^diff[ ]--git
167 [ ]a/(?P<a_path>.+?)[ ]b/(?P<b_path>.+?)\n
167 [ ]a/(?P<a_path>.+?)[ ]b/(?P<b_path>.+?)\n
168 (?:^similarity[ ]index[ ](?P<similarity_index>\d+)%\n
168 (?:^similarity[ ]index[ ](?P<similarity_index>\d+)%\n
169 ^rename[ ]from[ ](?P<rename_from>\S+)\n
169 ^rename[ ]from[ ](?P<rename_from>\S+)\n
170 ^rename[ ]to[ ](?P<rename_to>\S+)(?:\n|$))?
170 ^rename[ ]to[ ](?P<rename_to>\S+)(?:\n|$))?
171 (?:^old[ ]mode[ ](?P<old_mode>\d+)\n
171 (?:^old[ ]mode[ ](?P<old_mode>\d+)\n
172 ^new[ ]mode[ ](?P<new_mode>\d+)(?:\n|$))?
172 ^new[ ]mode[ ](?P<new_mode>\d+)(?:\n|$))?
173 (?:^new[ ]file[ ]mode[ ](?P<new_file_mode>.+)(?:\n|$))?
173 (?:^new[ ]file[ ]mode[ ](?P<new_file_mode>.+)(?:\n|$))?
174 (?:^deleted[ ]file[ ]mode[ ](?P<deleted_file_mode>.+)(?:\n|$))?
174 (?:^deleted[ ]file[ ]mode[ ](?P<deleted_file_mode>.+)(?:\n|$))?
175 (?:^index[ ](?P<a_blob_id>[0-9A-Fa-f]+)
175 (?:^index[ ](?P<a_blob_id>[0-9A-Fa-f]+)
176 \.\.(?P<b_blob_id>[0-9A-Fa-f]+)[ ]?(?P<b_mode>.+)?(?:\n|$))?
176 \.\.(?P<b_blob_id>[0-9A-Fa-f]+)[ ]?(?P<b_mode>.+)?(?:\n|$))?
177 (?:^---[ ](a/(?P<a_file>.+)|/dev/null)(?:\n|$))?
177 (?:^---[ ](a/(?P<a_file>.+)|/dev/null)(?:\n|$))?
178 (?:^\+\+\+[ ](b/(?P<b_file>.+)|/dev/null)(?:\n|$))?
178 (?:^\+\+\+[ ](b/(?P<b_file>.+)|/dev/null)(?:\n|$))?
179 """, re.VERBOSE | re.MULTILINE)
179 """, re.VERBOSE | re.MULTILINE)
180 _hg_header_re = re.compile(r"""
180 _hg_header_re = re.compile(r"""
181 #^diff[ ]--git
181 #^diff[ ]--git
182 [ ]a/(?P<a_path>.+?)[ ]b/(?P<b_path>.+?)\n
182 [ ]a/(?P<a_path>.+?)[ ]b/(?P<b_path>.+?)\n
183 (?:^similarity[ ]index[ ](?P<similarity_index>\d+)%(?:\n|$))?
183 (?:^similarity[ ]index[ ](?P<similarity_index>\d+)%(?:\n|$))?
184 (?:^rename[ ]from[ ](?P<rename_from>\S+)\n
184 (?:^rename[ ]from[ ](?P<rename_from>\S+)\n
185 ^rename[ ]to[ ](?P<rename_to>\S+)(?:\n|$))?
185 ^rename[ ]to[ ](?P<rename_to>\S+)(?:\n|$))?
186 (?:^old[ ]mode[ ](?P<old_mode>\d+)\n
186 (?:^old[ ]mode[ ](?P<old_mode>\d+)\n
187 ^new[ ]mode[ ](?P<new_mode>\d+)(?:\n|$))?
187 ^new[ ]mode[ ](?P<new_mode>\d+)(?:\n|$))?
188 (?:^new[ ]file[ ]mode[ ](?P<new_file_mode>.+)(?:\n|$))?
188 (?:^new[ ]file[ ]mode[ ](?P<new_file_mode>.+)(?:\n|$))?
189 (?:^deleted[ ]file[ ]mode[ ](?P<deleted_file_mode>.+)(?:\n|$))?
189 (?:^deleted[ ]file[ ]mode[ ](?P<deleted_file_mode>.+)(?:\n|$))?
190 (?:^index[ ](?P<a_blob_id>[0-9A-Fa-f]+)
190 (?:^index[ ](?P<a_blob_id>[0-9A-Fa-f]+)
191 \.\.(?P<b_blob_id>[0-9A-Fa-f]+)[ ]?(?P<b_mode>.+)?(?:\n|$))?
191 \.\.(?P<b_blob_id>[0-9A-Fa-f]+)[ ]?(?P<b_mode>.+)?(?:\n|$))?
192 (?:^---[ ](a/(?P<a_file>.+)|/dev/null)(?:\n|$))?
192 (?:^---[ ](a/(?P<a_file>.+)|/dev/null)(?:\n|$))?
193 (?:^\+\+\+[ ](b/(?P<b_file>.+)|/dev/null)(?:\n|$))?
193 (?:^\+\+\+[ ](b/(?P<b_file>.+)|/dev/null)(?:\n|$))?
194 """, re.VERBOSE | re.MULTILINE)
194 """, re.VERBOSE | re.MULTILINE)
195
195
196 def __init__(self, diff, vcs='hg', format='gitdiff', diff_limit=None):
196 def __init__(self, diff, vcs='hg', format='gitdiff', diff_limit=None):
197 """
197 """
198 :param diff: a text in diff format
198 :param diff: a text in diff format
199 :param vcs: type of version controll hg or git
199 :param vcs: type of version controll hg or git
200 :param format: format of diff passed, `udiff` or `gitdiff`
200 :param format: format of diff passed, `udiff` or `gitdiff`
201 :param diff_limit: define the size of diff that is considered "big"
201 :param diff_limit: define the size of diff that is considered "big"
202 based on that parameter cut off will be triggered, set to None
202 based on that parameter cut off will be triggered, set to None
203 to show full diff
203 to show full diff
204 """
204 """
205 if not isinstance(diff, basestring):
205 if not isinstance(diff, basestring):
206 raise Exception('Diff must be a basestring got %s instead' % type(diff))
206 raise Exception('Diff must be a basestring got %s instead' % type(diff))
207
207
208 self._diff = diff
208 self._diff = diff
209 self._format = format
209 self._format = format
210 self.adds = 0
210 self.adds = 0
211 self.removes = 0
211 self.removes = 0
212 # calculate diff size
212 # calculate diff size
213 self.diff_size = len(diff)
213 self.diff_size = len(diff)
214 self.diff_limit = diff_limit
214 self.diff_limit = diff_limit
215 self.cur_diff_size = 0
215 self.cur_diff_size = 0
216 self.parsed = False
216 self.parsed = False
217 self.parsed_diff = []
217 self.parsed_diff = []
218 self.vcs = vcs
218 self.vcs = vcs
219
219
220 if format == 'gitdiff':
220 if format == 'gitdiff':
221 self.differ = self._highlight_line_difflib
221 self.differ = self._highlight_line_difflib
222 self._parser = self._parse_gitdiff
222 self._parser = self._parse_gitdiff
223 else:
223 else:
224 self.differ = self._highlight_line_udiff
224 self.differ = self._highlight_line_udiff
225 self._parser = self._parse_udiff
225 self._parser = self._parse_udiff
226
226
227 def _copy_iterator(self):
227 def _copy_iterator(self):
228 """
228 """
229 make a fresh copy of generator, we should not iterate thru
229 make a fresh copy of generator, we should not iterate thru
230 an original as it's needed for repeating operations on
230 an original as it's needed for repeating operations on
231 this instance of DiffProcessor
231 this instance of DiffProcessor
232 """
232 """
233 self.__udiff, iterator_copy = tee(self.__udiff)
233 self.__udiff, iterator_copy = tee(self.__udiff)
234 return iterator_copy
234 return iterator_copy
235
235
236 def _escaper(self, string):
236 def _escaper(self, string):
237 """
237 """
238 Escaper for diff escapes special chars and checks the diff limit
238 Escaper for diff escapes special chars and checks the diff limit
239
239
240 :param string:
240 :param string:
241 :type string:
241 :type string:
242 """
242 """
243
243
244 self.cur_diff_size += len(string)
244 self.cur_diff_size += len(string)
245
245
246 # escaper get's iterated on each .next() call and it checks if each
246 # escaper get's iterated on each .next() call and it checks if each
247 # parsed line doesn't exceed the diff limit
247 # parsed line doesn't exceed the diff limit
248 if self.diff_limit is not None and self.cur_diff_size > self.diff_limit:
248 if self.diff_limit is not None and self.cur_diff_size > self.diff_limit:
249 raise DiffLimitExceeded('Diff Limit Exceeded')
249 raise DiffLimitExceeded('Diff Limit Exceeded')
250
250
251 return safe_unicode(string).replace('&', '&amp;')\
251 return safe_unicode(string).replace('&', '&amp;')\
252 .replace('<', '&lt;')\
252 .replace('<', '&lt;')\
253 .replace('>', '&gt;')
253 .replace('>', '&gt;')
254
254
255 def _line_counter(self, l):
255 def _line_counter(self, l):
256 """
256 """
257 Checks each line and bumps total adds/removes for this diff
257 Checks each line and bumps total adds/removes for this diff
258
258
259 :param l:
259 :param l:
260 """
260 """
261 if l.startswith('+') and not l.startswith('+++'):
261 if l.startswith('+') and not l.startswith('+++'):
262 self.adds += 1
262 self.adds += 1
263 elif l.startswith('-') and not l.startswith('---'):
263 elif l.startswith('-') and not l.startswith('---'):
264 self.removes += 1
264 self.removes += 1
265 return l
265 return safe_unicode(l)
266
266
267 def _highlight_line_difflib(self, line, next_):
267 def _highlight_line_difflib(self, line, next_):
268 """
268 """
269 Highlight inline changes in both lines.
269 Highlight inline changes in both lines.
270 """
270 """
271
271
272 if line['action'] == 'del':
272 if line['action'] == 'del':
273 old, new = line, next_
273 old, new = line, next_
274 else:
274 else:
275 old, new = next_, line
275 old, new = next_, line
276
276
277 oldwords = re.split(r'(\W)', old['line'])
277 oldwords = re.split(r'(\W)', old['line'])
278 newwords = re.split(r'(\W)', new['line'])
278 newwords = re.split(r'(\W)', new['line'])
279
279
280 sequence = difflib.SequenceMatcher(None, oldwords, newwords)
280 sequence = difflib.SequenceMatcher(None, oldwords, newwords)
281
281
282 oldfragments, newfragments = [], []
282 oldfragments, newfragments = [], []
283 for tag, i1, i2, j1, j2 in sequence.get_opcodes():
283 for tag, i1, i2, j1, j2 in sequence.get_opcodes():
284 oldfrag = ''.join(oldwords[i1:i2])
284 oldfrag = ''.join(oldwords[i1:i2])
285 newfrag = ''.join(newwords[j1:j2])
285 newfrag = ''.join(newwords[j1:j2])
286 if tag != 'equal':
286 if tag != 'equal':
287 if oldfrag:
287 if oldfrag:
288 oldfrag = '<del>%s</del>' % oldfrag
288 oldfrag = '<del>%s</del>' % oldfrag
289 if newfrag:
289 if newfrag:
290 newfrag = '<ins>%s</ins>' % newfrag
290 newfrag = '<ins>%s</ins>' % newfrag
291 oldfragments.append(oldfrag)
291 oldfragments.append(oldfrag)
292 newfragments.append(newfrag)
292 newfragments.append(newfrag)
293
293
294 old['line'] = "".join(oldfragments)
294 old['line'] = "".join(oldfragments)
295 new['line'] = "".join(newfragments)
295 new['line'] = "".join(newfragments)
296
296
297 def _highlight_line_udiff(self, line, next_):
297 def _highlight_line_udiff(self, line, next_):
298 """
298 """
299 Highlight inline changes in both lines.
299 Highlight inline changes in both lines.
300 """
300 """
301 start = 0
301 start = 0
302 limit = min(len(line['line']), len(next_['line']))
302 limit = min(len(line['line']), len(next_['line']))
303 while start < limit and line['line'][start] == next_['line'][start]:
303 while start < limit and line['line'][start] == next_['line'][start]:
304 start += 1
304 start += 1
305 end = -1
305 end = -1
306 limit -= start
306 limit -= start
307 while -end <= limit and line['line'][end] == next_['line'][end]:
307 while -end <= limit and line['line'][end] == next_['line'][end]:
308 end -= 1
308 end -= 1
309 end += 1
309 end += 1
310 if start or end:
310 if start or end:
311 def do(l):
311 def do(l):
312 last = end + len(l['line'])
312 last = end + len(l['line'])
313 if l['action'] == 'add':
313 if l['action'] == 'add':
314 tag = 'ins'
314 tag = 'ins'
315 else:
315 else:
316 tag = 'del'
316 tag = 'del'
317 l['line'] = '%s<%s>%s</%s>%s' % (
317 l['line'] = '%s<%s>%s</%s>%s' % (
318 l['line'][:start],
318 l['line'][:start],
319 tag,
319 tag,
320 l['line'][start:last],
320 l['line'][start:last],
321 tag,
321 tag,
322 l['line'][last:]
322 l['line'][last:]
323 )
323 )
324 do(line)
324 do(line)
325 do(next_)
325 do(next_)
326
326
327 def _get_header(self, diff_chunk):
327 def _get_header(self, diff_chunk):
328 """
328 """
329 parses the diff header, and returns parts, and leftover diff
329 parses the diff header, and returns parts, and leftover diff
330 parts consists of 14 elements::
330 parts consists of 14 elements::
331
331
332 a_path, b_path, similarity_index, rename_from, rename_to,
332 a_path, b_path, similarity_index, rename_from, rename_to,
333 old_mode, new_mode, new_file_mode, deleted_file_mode,
333 old_mode, new_mode, new_file_mode, deleted_file_mode,
334 a_blob_id, b_blob_id, b_mode, a_file, b_file
334 a_blob_id, b_blob_id, b_mode, a_file, b_file
335
335
336 :param diff_chunk:
336 :param diff_chunk:
337 :type diff_chunk:
337 :type diff_chunk:
338 """
338 """
339
339
340 if self.vcs == 'git':
340 if self.vcs == 'git':
341 match = self._git_header_re.match(diff_chunk)
341 match = self._git_header_re.match(diff_chunk)
342 diff = diff_chunk[match.end():]
342 diff = diff_chunk[match.end():]
343 return match.groupdict(), imap(self._escaper, diff.splitlines(1))
343 return match.groupdict(), imap(self._escaper, diff.splitlines(1))
344 elif self.vcs == 'hg':
344 elif self.vcs == 'hg':
345 match = self._hg_header_re.match(diff_chunk)
345 match = self._hg_header_re.match(diff_chunk)
346 diff = diff_chunk[match.end():]
346 diff = diff_chunk[match.end():]
347 return match.groupdict(), imap(self._escaper, diff.splitlines(1))
347 return match.groupdict(), imap(self._escaper, diff.splitlines(1))
348 else:
348 else:
349 raise Exception('VCS type %s is not supported' % self.vcs)
349 raise Exception('VCS type %s is not supported' % self.vcs)
350
350
351 def _parse_gitdiff(self, inline_diff=True):
351 def _parse_gitdiff(self, inline_diff=True):
352 _files = []
352 _files = []
353 diff_container = lambda arg: arg
353 diff_container = lambda arg: arg
354
354
355 ##split the diff in chunks of separate --git a/file b/file chunks
355 ##split the diff in chunks of separate --git a/file b/file chunks
356 for raw_diff in ('\n' + self._diff).split('\ndiff --git')[1:]:
356 for raw_diff in ('\n' + self._diff).split('\ndiff --git')[1:]:
357 binary = False
357 binary = False
358 binary_msg = 'unknown binary'
358 binary_msg = 'unknown binary'
359 head, diff = self._get_header(raw_diff)
359 head, diff = self._get_header(raw_diff)
360
360
361 if not head['a_file'] and head['b_file']:
361 if not head['a_file'] and head['b_file']:
362 op = 'A'
362 op = 'A'
363 elif head['a_file'] and head['b_file']:
363 elif head['a_file'] and head['b_file']:
364 op = 'M'
364 op = 'M'
365 elif head['a_file'] and not head['b_file']:
365 elif head['a_file'] and not head['b_file']:
366 op = 'D'
366 op = 'D'
367 else:
367 else:
368 #probably we're dealing with a binary file 1
368 #probably we're dealing with a binary file 1
369 binary = True
369 binary = True
370 if head['deleted_file_mode']:
370 if head['deleted_file_mode']:
371 op = 'D'
371 op = 'D'
372 stats = ['b', DEL_FILENODE]
372 stats = ['b', DEL_FILENODE]
373 binary_msg = 'deleted binary file'
373 binary_msg = 'deleted binary file'
374 elif head['new_file_mode']:
374 elif head['new_file_mode']:
375 op = 'A'
375 op = 'A'
376 stats = ['b', NEW_FILENODE]
376 stats = ['b', NEW_FILENODE]
377 binary_msg = 'new binary file %s' % head['new_file_mode']
377 binary_msg = 'new binary file %s' % head['new_file_mode']
378 else:
378 else:
379 if head['new_mode'] and head['old_mode']:
379 if head['new_mode'] and head['old_mode']:
380 stats = ['b', CHMOD_FILENODE]
380 stats = ['b', CHMOD_FILENODE]
381 op = 'M'
381 op = 'M'
382 binary_msg = ('modified binary file chmod %s => %s'
382 binary_msg = ('modified binary file chmod %s => %s'
383 % (head['old_mode'], head['new_mode']))
383 % (head['old_mode'], head['new_mode']))
384 elif (head['rename_from'] and head['rename_to']
384 elif (head['rename_from'] and head['rename_to']
385 and head['rename_from'] != head['rename_to']):
385 and head['rename_from'] != head['rename_to']):
386 stats = ['b', RENAMED_FILENODE]
386 stats = ['b', RENAMED_FILENODE]
387 op = 'M'
387 op = 'M'
388 binary_msg = ('file renamed from %s to %s'
388 binary_msg = ('file renamed from %s to %s'
389 % (head['rename_from'], head['rename_to']))
389 % (head['rename_from'], head['rename_to']))
390 else:
390 else:
391 stats = ['b', MOD_FILENODE]
391 stats = ['b', MOD_FILENODE]
392 op = 'M'
392 op = 'M'
393 binary_msg = 'modified binary file'
393 binary_msg = 'modified binary file'
394
394
395 if not binary:
395 if not binary:
396 try:
396 try:
397 chunks, stats = self._parse_lines(diff)
397 chunks, stats = self._parse_lines(diff)
398 except DiffLimitExceeded:
398 except DiffLimitExceeded:
399 diff_container = lambda _diff: LimitedDiffContainer(
399 diff_container = lambda _diff: LimitedDiffContainer(
400 self.diff_limit,
400 self.diff_limit,
401 self.cur_diff_size,
401 self.cur_diff_size,
402 _diff)
402 _diff)
403 break
403 break
404 else:
404 else:
405 chunks = []
405 chunks = []
406 chunks.append([{
406 chunks.append([{
407 'old_lineno': '',
407 'old_lineno': '',
408 'new_lineno': '',
408 'new_lineno': '',
409 'action': 'binary',
409 'action': 'binary',
410 'line': binary_msg,
410 'line': binary_msg,
411 }])
411 }])
412
412
413 _files.append({
413 _files.append({
414 'filename': head['b_path'],
414 'filename': head['b_path'],
415 'old_revision': head['a_blob_id'],
415 'old_revision': head['a_blob_id'],
416 'new_revision': head['b_blob_id'],
416 'new_revision': head['b_blob_id'],
417 'chunks': chunks,
417 'chunks': chunks,
418 'operation': op,
418 'operation': op,
419 'stats': stats,
419 'stats': stats,
420 })
420 })
421
421
422 sorter = lambda info: {'A': 0, 'M': 1, 'D': 2}.get(info['operation'])
422 sorter = lambda info: {'A': 0, 'M': 1, 'D': 2}.get(info['operation'])
423
423
424 if inline_diff is False:
424 if inline_diff is False:
425 return diff_container(sorted(_files, key=sorter))
425 return diff_container(sorted(_files, key=sorter))
426
426
427 # highlight inline changes
427 # highlight inline changes
428 for diff_data in _files:
428 for diff_data in _files:
429 for chunk in diff_data['chunks']:
429 for chunk in diff_data['chunks']:
430 lineiter = iter(chunk)
430 lineiter = iter(chunk)
431 try:
431 try:
432 while 1:
432 while 1:
433 line = lineiter.next()
433 line = lineiter.next()
434 if line['action'] not in ['unmod', 'context']:
434 if line['action'] not in ['unmod', 'context']:
435 nextline = lineiter.next()
435 nextline = lineiter.next()
436 if nextline['action'] in ['unmod', 'context'] or \
436 if nextline['action'] in ['unmod', 'context'] or \
437 nextline['action'] == line['action']:
437 nextline['action'] == line['action']:
438 continue
438 continue
439 self.differ(line, nextline)
439 self.differ(line, nextline)
440 except StopIteration:
440 except StopIteration:
441 pass
441 pass
442
442
443 return diff_container(sorted(_files, key=sorter))
443 return diff_container(sorted(_files, key=sorter))
444
444
445 def _parse_udiff(self, inline_diff=True):
445 def _parse_udiff(self, inline_diff=True):
446 raise NotImplementedError()
446 raise NotImplementedError()
447
447
448 def _parse_lines(self, diff):
448 def _parse_lines(self, diff):
449 """
449 """
450 Parse the diff an return data for the template.
450 Parse the diff an return data for the template.
451 """
451 """
452
452
453 lineiter = iter(diff)
453 lineiter = iter(diff)
454 stats = [0, 0]
454 stats = [0, 0]
455
455
456 try:
456 try:
457 chunks = []
457 chunks = []
458 line = lineiter.next()
458 line = lineiter.next()
459
459
460 while line:
460 while line:
461 lines = []
461 lines = []
462 chunks.append(lines)
462 chunks.append(lines)
463
463
464 match = self._chunk_re.match(line)
464 match = self._chunk_re.match(line)
465
465
466 if not match:
466 if not match:
467 break
467 break
468
468
469 gr = match.groups()
469 gr = match.groups()
470 (old_line, old_end,
470 (old_line, old_end,
471 new_line, new_end) = [int(x or 1) for x in gr[:-1]]
471 new_line, new_end) = [int(x or 1) for x in gr[:-1]]
472 old_line -= 1
472 old_line -= 1
473 new_line -= 1
473 new_line -= 1
474
474
475 context = len(gr) == 5
475 context = len(gr) == 5
476 old_end += old_line
476 old_end += old_line
477 new_end += new_line
477 new_end += new_line
478
478
479 if context:
479 if context:
480 # skip context only if it's first line
480 # skip context only if it's first line
481 if int(gr[0]) > 1:
481 if int(gr[0]) > 1:
482 lines.append({
482 lines.append({
483 'old_lineno': '...',
483 'old_lineno': '...',
484 'new_lineno': '...',
484 'new_lineno': '...',
485 'action': 'context',
485 'action': 'context',
486 'line': line,
486 'line': line,
487 })
487 })
488
488
489 line = lineiter.next()
489 line = lineiter.next()
490
490
491 while old_line < old_end or new_line < new_end:
491 while old_line < old_end or new_line < new_end:
492 if line:
492 if line:
493 command = line[0]
493 command = line[0]
494 if command in ['+', '-', ' ']:
494 if command in ['+', '-', ' ']:
495 #only modify the line if it's actually a diff
495 #only modify the line if it's actually a diff
496 # thing
496 # thing
497 line = line[1:]
497 line = line[1:]
498 else:
498 else:
499 command = ' '
499 command = ' '
500
500
501 affects_old = affects_new = False
501 affects_old = affects_new = False
502
502
503 # ignore those if we don't expect them
503 # ignore those if we don't expect them
504 if command in '#@':
504 if command in '#@':
505 continue
505 continue
506 elif command == '+':
506 elif command == '+':
507 affects_new = True
507 affects_new = True
508 action = 'add'
508 action = 'add'
509 stats[0] += 1
509 stats[0] += 1
510 elif command == '-':
510 elif command == '-':
511 affects_old = True
511 affects_old = True
512 action = 'del'
512 action = 'del'
513 stats[1] += 1
513 stats[1] += 1
514 else:
514 else:
515 affects_old = affects_new = True
515 affects_old = affects_new = True
516 action = 'unmod'
516 action = 'unmod'
517
517
518 if line != self._newline_marker:
518 if line != self._newline_marker:
519 old_line += affects_old
519 old_line += affects_old
520 new_line += affects_new
520 new_line += affects_new
521 lines.append({
521 lines.append({
522 'old_lineno': affects_old and old_line or '',
522 'old_lineno': affects_old and old_line or '',
523 'new_lineno': affects_new and new_line or '',
523 'new_lineno': affects_new and new_line or '',
524 'action': action,
524 'action': action,
525 'line': line
525 'line': line
526 })
526 })
527
527
528 line = lineiter.next()
528 line = lineiter.next()
529
529
530 if line == self._newline_marker:
530 if line == self._newline_marker:
531 # we need to append to lines, since this is not
531 # we need to append to lines, since this is not
532 # counted in the line specs of diff
532 # counted in the line specs of diff
533 lines.append({
533 lines.append({
534 'old_lineno': '...',
534 'old_lineno': '...',
535 'new_lineno': '...',
535 'new_lineno': '...',
536 'action': 'context',
536 'action': 'context',
537 'line': line
537 'line': line
538 })
538 })
539
539
540 except StopIteration:
540 except StopIteration:
541 pass
541 pass
542 return chunks, stats
542 return chunks, stats
543
543
544 def _safe_id(self, idstring):
544 def _safe_id(self, idstring):
545 """Make a string safe for including in an id attribute.
545 """Make a string safe for including in an id attribute.
546
546
547 The HTML spec says that id attributes 'must begin with
547 The HTML spec says that id attributes 'must begin with
548 a letter ([A-Za-z]) and may be followed by any number
548 a letter ([A-Za-z]) and may be followed by any number
549 of letters, digits ([0-9]), hyphens ("-"), underscores
549 of letters, digits ([0-9]), hyphens ("-"), underscores
550 ("_"), colons (":"), and periods (".")'. These regexps
550 ("_"), colons (":"), and periods (".")'. These regexps
551 are slightly over-zealous, in that they remove colons
551 are slightly over-zealous, in that they remove colons
552 and periods unnecessarily.
552 and periods unnecessarily.
553
553
554 Whitespace is transformed into underscores, and then
554 Whitespace is transformed into underscores, and then
555 anything which is not a hyphen or a character that
555 anything which is not a hyphen or a character that
556 matches \w (alphanumerics and underscore) is removed.
556 matches \w (alphanumerics and underscore) is removed.
557
557
558 """
558 """
559 # Transform all whitespace to underscore
559 # Transform all whitespace to underscore
560 idstring = re.sub(r'\s', "_", '%s' % idstring)
560 idstring = re.sub(r'\s', "_", '%s' % idstring)
561 # Remove everything that is not a hyphen or a member of \w
561 # Remove everything that is not a hyphen or a member of \w
562 idstring = re.sub(r'(?!-)\W', "", idstring).lower()
562 idstring = re.sub(r'(?!-)\W', "", idstring).lower()
563 return idstring
563 return idstring
564
564
565 def prepare(self, inline_diff=True):
565 def prepare(self, inline_diff=True):
566 """
566 """
567 Prepare the passed udiff for HTML rendering. It'l return a list
567 Prepare the passed udiff for HTML rendering. It'l return a list
568 of dicts with diff information
568 of dicts with diff information
569 """
569 """
570 parsed = self._parser(inline_diff=inline_diff)
570 parsed = self._parser(inline_diff=inline_diff)
571 self.parsed = True
571 self.parsed = True
572 self.parsed_diff = parsed
572 self.parsed_diff = parsed
573 return parsed
573 return parsed
574
574
575 def as_raw(self, diff_lines=None):
575 def as_raw(self, diff_lines=None):
576 """
576 """
577 Returns raw string as udiff
577 Returns raw string diff
578 """
578 """
579 return u''.join(imap(self._line_counter, self._diff.splitlines(1)))
579 return self._diff
580 #return u''.join(imap(self._line_counter, self._diff.splitlines(1)))
580
581
581 def as_html(self, table_class='code-difftable', line_class='line',
582 def as_html(self, table_class='code-difftable', line_class='line',
582 new_lineno_class='lineno old', old_lineno_class='lineno new',
583 new_lineno_class='lineno old', old_lineno_class='lineno new',
583 code_class='code', enable_comments=False, parsed_lines=None):
584 code_class='code', enable_comments=False, parsed_lines=None):
584 """
585 """
585 Return given diff as html table with customized css classes
586 Return given diff as html table with customized css classes
586 """
587 """
587 def _link_to_if(condition, label, url):
588 def _link_to_if(condition, label, url):
588 """
589 """
589 Generates a link if condition is meet or just the label if not.
590 Generates a link if condition is meet or just the label if not.
590 """
591 """
591
592
592 if condition:
593 if condition:
593 return '''<a href="%(url)s">%(label)s</a>''' % {
594 return '''<a href="%(url)s">%(label)s</a>''' % {
594 'url': url,
595 'url': url,
595 'label': label
596 'label': label
596 }
597 }
597 else:
598 else:
598 return label
599 return label
599 if not self.parsed:
600 if not self.parsed:
600 self.prepare()
601 self.prepare()
601
602
602 diff_lines = self.parsed_diff
603 diff_lines = self.parsed_diff
603 if parsed_lines:
604 if parsed_lines:
604 diff_lines = parsed_lines
605 diff_lines = parsed_lines
605
606
606 _html_empty = True
607 _html_empty = True
607 _html = []
608 _html = []
608 _html.append('''<table class="%(table_class)s">\n''' % {
609 _html.append('''<table class="%(table_class)s">\n''' % {
609 'table_class': table_class
610 'table_class': table_class
610 })
611 })
611
612
612 for diff in diff_lines:
613 for diff in diff_lines:
613 for line in diff['chunks']:
614 for line in diff['chunks']:
614 _html_empty = False
615 _html_empty = False
615 for change in line:
616 for change in line:
616 _html.append('''<tr class="%(lc)s %(action)s">\n''' % {
617 _html.append('''<tr class="%(lc)s %(action)s">\n''' % {
617 'lc': line_class,
618 'lc': line_class,
618 'action': change['action']
619 'action': change['action']
619 })
620 })
620 anchor_old_id = ''
621 anchor_old_id = ''
621 anchor_new_id = ''
622 anchor_new_id = ''
622 anchor_old = "%(filename)s_o%(oldline_no)s" % {
623 anchor_old = "%(filename)s_o%(oldline_no)s" % {
623 'filename': self._safe_id(diff['filename']),
624 'filename': self._safe_id(diff['filename']),
624 'oldline_no': change['old_lineno']
625 'oldline_no': change['old_lineno']
625 }
626 }
626 anchor_new = "%(filename)s_n%(oldline_no)s" % {
627 anchor_new = "%(filename)s_n%(oldline_no)s" % {
627 'filename': self._safe_id(diff['filename']),
628 'filename': self._safe_id(diff['filename']),
628 'oldline_no': change['new_lineno']
629 'oldline_no': change['new_lineno']
629 }
630 }
630 cond_old = (change['old_lineno'] != '...' and
631 cond_old = (change['old_lineno'] != '...' and
631 change['old_lineno'])
632 change['old_lineno'])
632 cond_new = (change['new_lineno'] != '...' and
633 cond_new = (change['new_lineno'] != '...' and
633 change['new_lineno'])
634 change['new_lineno'])
634 if cond_old:
635 if cond_old:
635 anchor_old_id = 'id="%s"' % anchor_old
636 anchor_old_id = 'id="%s"' % anchor_old
636 if cond_new:
637 if cond_new:
637 anchor_new_id = 'id="%s"' % anchor_new
638 anchor_new_id = 'id="%s"' % anchor_new
638 ###########################################################
639 ###########################################################
639 # OLD LINE NUMBER
640 # OLD LINE NUMBER
640 ###########################################################
641 ###########################################################
641 _html.append('''\t<td %(a_id)s class="%(olc)s">''' % {
642 _html.append('''\t<td %(a_id)s class="%(olc)s">''' % {
642 'a_id': anchor_old_id,
643 'a_id': anchor_old_id,
643 'olc': old_lineno_class
644 'olc': old_lineno_class
644 })
645 })
645
646
646 _html.append('''%(link)s''' % {
647 _html.append('''%(link)s''' % {
647 'link': _link_to_if(True, change['old_lineno'],
648 'link': _link_to_if(True, change['old_lineno'],
648 '#%s' % anchor_old)
649 '#%s' % anchor_old)
649 })
650 })
650 _html.append('''</td>\n''')
651 _html.append('''</td>\n''')
651 ###########################################################
652 ###########################################################
652 # NEW LINE NUMBER
653 # NEW LINE NUMBER
653 ###########################################################
654 ###########################################################
654
655
655 _html.append('''\t<td %(a_id)s class="%(nlc)s">''' % {
656 _html.append('''\t<td %(a_id)s class="%(nlc)s">''' % {
656 'a_id': anchor_new_id,
657 'a_id': anchor_new_id,
657 'nlc': new_lineno_class
658 'nlc': new_lineno_class
658 })
659 })
659
660
660 _html.append('''%(link)s''' % {
661 _html.append('''%(link)s''' % {
661 'link': _link_to_if(True, change['new_lineno'],
662 'link': _link_to_if(True, change['new_lineno'],
662 '#%s' % anchor_new)
663 '#%s' % anchor_new)
663 })
664 })
664 _html.append('''</td>\n''')
665 _html.append('''</td>\n''')
665 ###########################################################
666 ###########################################################
666 # CODE
667 # CODE
667 ###########################################################
668 ###########################################################
668 comments = '' if enable_comments else 'no-comment'
669 comments = '' if enable_comments else 'no-comment'
669 _html.append('''\t<td class="%(cc)s %(inc)s">''' % {
670 _html.append('''\t<td class="%(cc)s %(inc)s">''' % {
670 'cc': code_class,
671 'cc': code_class,
671 'inc': comments
672 'inc': comments
672 })
673 })
673 _html.append('''\n\t\t<pre>%(code)s</pre>\n''' % {
674 _html.append('''\n\t\t<pre>%(code)s</pre>\n''' % {
674 'code': change['line']
675 'code': change['line']
675 })
676 })
676
677
677 _html.append('''\t</td>''')
678 _html.append('''\t</td>''')
678 _html.append('''\n</tr>\n''')
679 _html.append('''\n</tr>\n''')
679 _html.append('''</table>''')
680 _html.append('''</table>''')
680 if _html_empty:
681 if _html_empty:
681 return None
682 return None
682 return ''.join(_html)
683 return ''.join(_html)
683
684
684 def stat(self):
685 def stat(self):
685 """
686 """
686 Returns tuple of added, and removed lines for this instance
687 Returns tuple of added, and removed lines for this instance
687 """
688 """
688 return self.adds, self.removes
689 return self.adds, self.removes
689
690
690
691
691 class InMemoryBundleRepo(bundlerepository):
692 class InMemoryBundleRepo(bundlerepository):
692 def __init__(self, ui, path, bundlestream):
693 def __init__(self, ui, path, bundlestream):
693 self._tempparent = None
694 self._tempparent = None
694 localrepo.localrepository.__init__(self, ui, path)
695 localrepo.localrepository.__init__(self, ui, path)
695 self.ui.setconfig('phases', 'publish', False)
696 self.ui.setconfig('phases', 'publish', False)
696
697
697 self.bundle = bundlestream
698 self.bundle = bundlestream
698
699
699 # dict with the mapping 'filename' -> position in the bundle
700 # dict with the mapping 'filename' -> position in the bundle
700 self.bundlefilespos = {}
701 self.bundlefilespos = {}
701
702
702
703
703 def differ(org_repo, org_ref, other_repo, other_ref, discovery_data=None,
704 def differ(org_repo, org_ref, other_repo, other_ref, discovery_data=None,
704 bundle_compare=False, context=3, ignore_whitespace=False):
705 bundle_compare=False, context=3, ignore_whitespace=False):
705 """
706 """
706 General differ between branches, bookmarks, revisions of two remote related
707 General differ between branches, bookmarks, revisions of two remote related
707 repositories
708 repositories
708
709
709 :param org_repo:
710 :param org_repo:
710 :type org_repo:
711 :type org_repo:
711 :param org_ref:
712 :param org_ref:
712 :type org_ref:
713 :type org_ref:
713 :param other_repo:
714 :param other_repo:
714 :type other_repo:
715 :type other_repo:
715 :param other_ref:
716 :param other_ref:
716 :type other_ref:
717 :type other_ref:
717 """
718 """
718
719
719 bundlerepo = None
720 bundlerepo = None
720 ignore_whitespace = ignore_whitespace
721 ignore_whitespace = ignore_whitespace
721 context = context
722 context = context
722 org_repo = org_repo.scm_instance._repo
723 org_repo = org_repo.scm_instance._repo
723 other_repo = other_repo.scm_instance._repo
724 other_repo = other_repo.scm_instance._repo
724 opts = diffopts(git=True, ignorews=ignore_whitespace, context=context)
725 opts = diffopts(git=True, ignorews=ignore_whitespace, context=context)
725 org_ref = org_ref[1]
726 org_ref = org_ref[1]
726 other_ref = other_ref[1]
727 other_ref = other_ref[1]
727
728
728 if org_repo != other_repo and bundle_compare:
729 if org_repo != other_repo and bundle_compare:
729
730
730 common, incoming, rheads = discovery_data
731 common, incoming, rheads = discovery_data
731 other_repo_peer = localrepo.locallegacypeer(other_repo.local())
732 other_repo_peer = localrepo.locallegacypeer(other_repo.local())
732 # create a bundle (uncompressed if other repo is not local)
733 # create a bundle (uncompressed if other repo is not local)
733 if other_repo_peer.capable('getbundle') and incoming:
734 if other_repo_peer.capable('getbundle') and incoming:
734 # disable repo hooks here since it's just bundle !
735 # disable repo hooks here since it's just bundle !
735 # patch and reset hooks section of UI config to not run any
736 # patch and reset hooks section of UI config to not run any
736 # hooks on fetching archives with subrepos
737 # hooks on fetching archives with subrepos
737 for k, _ in other_repo.ui.configitems('hooks'):
738 for k, _ in other_repo.ui.configitems('hooks'):
738 other_repo.ui.setconfig('hooks', k, None)
739 other_repo.ui.setconfig('hooks', k, None)
739
740
740 unbundle = other_repo.getbundle('incoming', common=common,
741 unbundle = other_repo.getbundle('incoming', common=common,
741 heads=None)
742 heads=None)
742
743
743 buf = BytesIO()
744 buf = BytesIO()
744 while True:
745 while True:
745 chunk = unbundle._stream.read(1024 * 4)
746 chunk = unbundle._stream.read(1024 * 4)
746 if not chunk:
747 if not chunk:
747 break
748 break
748 buf.write(chunk)
749 buf.write(chunk)
749
750
750 buf.seek(0)
751 buf.seek(0)
751 # replace chunked _stream with data that can do tell() and seek()
752 # replace chunked _stream with data that can do tell() and seek()
752 unbundle._stream = buf
753 unbundle._stream = buf
753
754
754 ui = make_ui('db')
755 ui = make_ui('db')
755 bundlerepo = InMemoryBundleRepo(ui, path=org_repo.root,
756 bundlerepo = InMemoryBundleRepo(ui, path=org_repo.root,
756 bundlestream=unbundle)
757 bundlestream=unbundle)
757
758
758 return ''.join(patch.diff(bundlerepo or org_repo,
759 return ''.join(patch.diff(bundlerepo or org_repo,
759 node1=org_repo[org_ref].node(),
760 node1=org_repo[org_ref].node(),
760 node2=other_repo[other_ref].node(),
761 node2=other_repo[other_ref].node(),
761 opts=opts))
762 opts=opts))
762 else:
763 else:
763 log.debug('running diff between %s@%s and %s@%s'
764 log.debug('running diff between %s@%s and %s@%s'
764 % (org_repo, org_ref, other_repo, other_ref))
765 % (org_repo, org_ref, other_repo, other_ref))
765 return ''.join(patch.diff(org_repo, node1=org_ref, node2=other_ref,
766 return ''.join(patch.diff(org_repo, node1=org_ref, node2=other_ref,
766 opts=opts))
767 opts=opts))
@@ -1,302 +1,303 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2
2
3 <%inherit file="/base/base.html"/>
3 <%inherit file="/base/base.html"/>
4
4
5 <%def name="title()">
5 <%def name="title()">
6 ${_('%s Changelog') % c.repo_name} - ${c.rhodecode_name}
6 ${_('%s Changelog') % c.repo_name} - ${c.rhodecode_name}
7 </%def>
7 </%def>
8
8
9 <%def name="breadcrumbs_links()">
9 <%def name="breadcrumbs_links()">
10 ${h.link_to(_(u'Home'),h.url('/'))}
10 ${h.link_to(_(u'Home'),h.url('/'))}
11 &raquo;
11 &raquo;
12 ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
12 ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
13 &raquo;
13 &raquo;
14 <% size = c.size if c.size <= c.total_cs else c.total_cs %>
14 <% size = c.size if c.size <= c.total_cs else c.total_cs %>
15 ${_('Changelog')} - ${ungettext('showing %d out of %d revision', 'showing %d out of %d revisions', size) % (size, c.total_cs)}
15 ${_('Changelog')} - ${ungettext('showing %d out of %d revision', 'showing %d out of %d revisions', size) % (size, c.total_cs)}
16 </%def>
16 </%def>
17
17
18 <%def name="page_nav()">
18 <%def name="page_nav()">
19 ${self.menu('changelog')}
19 ${self.menu('changelog')}
20 </%def>
20 </%def>
21
21
22 <%def name="main()">
22 <%def name="main()">
23 <div class="box">
23 <div class="box">
24 <!-- box / title -->
24 <!-- box / title -->
25 <div class="title">
25 <div class="title">
26 ${self.breadcrumbs()}
26 ${self.breadcrumbs()}
27 </div>
27 </div>
28 <div class="table">
28 <div class="table">
29 % if c.pagination:
29 % if c.pagination:
30 <div id="graph">
30 <div id="graph">
31 <div id="graph_nodes">
31 <div id="graph_nodes">
32 <canvas id="graph_canvas"></canvas>
32 <canvas id="graph_canvas"></canvas>
33 </div>
33 </div>
34 <div id="graph_content">
34 <div id="graph_content">
35 <div class="info_box" style="clear: both;padding: 10px 6px;vertical-align: right;text-align: right;">
35 <div class="info_box" style="clear: both;padding: 10px 6px;vertical-align: right;text-align: right;">
36 <a href="#" class="ui-btn small" id="rev_range_container" style="display:none"></a>
36 <a href="#" class="ui-btn small" id="rev_range_container" style="display:none"></a>
37 <a href="#" class="ui-btn small" id="rev_range_clear" style="display:none">${_('Clear selection')}</a>
37 <a href="#" class="ui-btn small" id="rev_range_clear" style="display:none">${_('Clear selection')}</a>
38
38
39 %if c.rhodecode_db_repo.fork:
39 %if c.rhodecode_db_repo.fork:
40 <a title="${_('compare fork with %s' % c.rhodecode_db_repo.fork.repo_name)}" href="${h.url('compare_url',repo_name=c.repo_name,org_ref_type='branch',org_ref=request.GET.get('branch') or 'default',other_ref_type='branch',other_ref='default',repo=c.rhodecode_db_repo.fork.repo_name)}" class="ui-btn small">${_('Compare fork')}</a>
40 <a title="${_('compare fork with %s' % c.rhodecode_db_repo.fork.repo_name)}" href="${h.url('compare_url',repo_name=c.repo_name,org_ref_type='branch',org_ref=request.GET.get('branch') or 'default',other_ref_type='branch',other_ref='default',repo=c.rhodecode_db_repo.fork.repo_name)}" class="ui-btn small">${_('Compare fork')}</a>
41 %endif
41 %endif
42 %if h.is_hg(c.rhodecode_repo):
42 %if h.is_hg(c.rhodecode_repo):
43 <a id="open_new_pr" href="${h.url('pullrequest_home',repo_name=c.repo_name)}" class="ui-btn small">${_('Open new pull request')}</a>
43 <a id="open_new_pr" href="${h.url('pullrequest_home',repo_name=c.repo_name)}" class="ui-btn small">${_('Open new pull request')}</a>
44 %endif
44 %endif
45 </div>
45 </div>
46 <div class="container_header">
46 <div class="container_header">
47 ${h.form(h.url.current(),method='get')}
47 ${h.form(h.url.current(),method='get')}
48 <div class="info_box" style="float:left">
48 <div class="info_box" style="float:left">
49 ${h.submit('set',_('Show'),class_="ui-btn")}
49 ${h.submit('set',_('Show'),class_="ui-btn")}
50 ${h.text('size',size=1,value=c.size)}
50 ${h.text('size',size=1,value=c.size)}
51 ${_('revisions')}
51 ${_('revisions')}
52 </div>
52 </div>
53 ${h.end_form()}
53 ${h.end_form()}
54 <div style="float:right">${h.select('branch_filter',c.branch_name,c.branch_filters)}</div>
54 <div style="float:right">${h.select('branch_filter',c.branch_name,c.branch_filters)}</div>
55 </div>
55 </div>
56
56
57 %for cnt,cs in enumerate(c.pagination):
57 %for cnt,cs in enumerate(c.pagination):
58 <div id="chg_${cnt+1}" class="container ${'tablerow%s' % (cnt%2)}">
58 <div id="chg_${cnt+1}" class="container ${'tablerow%s' % (cnt%2)}">
59 <div class="left">
59 <div class="left">
60 <div>
60 <div>
61 ${h.checkbox(cs.raw_id,class_="changeset_range")}
61 ${h.checkbox(cs.raw_id,class_="changeset_range")}
62 <span class="tooltip" title="${h.tooltip(h.age(cs.date))}"><a href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id)}"><span class="changeset_id">${cs.revision}:<span class="changeset_hash">${h.short_id(cs.raw_id)}</span></span></a></span>
62 <span class="tooltip" title="${h.tooltip(h.age(cs.date))}"><a href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id)}"><span class="changeset_id">${cs.revision}:<span class="changeset_hash">${h.short_id(cs.raw_id)}</span></span></a></span>
63 </div>
63 </div>
64 <div class="author">
64 <div class="author">
65 <div class="gravatar">
65 <div class="gravatar">
66 <img alt="gravatar" src="${h.gravatar_url(h.email_or_none(cs.author),16)}"/>
66 <img alt="gravatar" src="${h.gravatar_url(h.email_or_none(cs.author),16)}"/>
67 </div>
67 </div>
68 <div title="${cs.author}" class="user">${h.shorter(h.person(cs.author),22)}</div>
68 <div title="${cs.author}" class="user">${h.shorter(h.person(cs.author),22)}</div>
69 </div>
69 </div>
70 <div class="date">${h.fmt_date(cs.date)}</div>
70 <div class="date">${h.fmt_date(cs.date)}</div>
71 </div>
71 </div>
72 <div class="mid">
72 <div class="mid">
73 <div class="message">${h.urlify_commit(cs.message, c.repo_name,h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}</div>
73 <div class="message">${h.urlify_commit(cs.message, c.repo_name,h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}</div>
74 <div class="expand"><span class="expandtext">&darr; ${_('show more')} &darr;</span></div>
74 <div class="expand"><span class="expandtext">&darr; ${_('show more')} &darr;</span></div>
75 </div>
75 </div>
76 <div class="right">
76 <div class="right">
77 <div class="changes">
77 <div class="changes">
78 <div id="changed_total_${cs.raw_id}" style="float:right;" class="changed_total tooltip" title="${h.tooltip(_('Affected number of files, click to show more details'))}">${len(cs.affected_files)}</div>
78 <div id="changed_total_${cs.raw_id}" style="float:right;" class="changed_total tooltip" title="${h.tooltip(_('Affected number of files, click to show more details'))}">${len(cs.affected_files)}</div>
79 <div class="comments-container">
79 <div class="comments-container">
80 %if len(c.comments.get(cs.raw_id,[])) > 0:
80 %if len(c.comments.get(cs.raw_id,[])) > 0:
81 <div class="comments-cnt" title="${('comments')}">
81 <div class="comments-cnt" title="${('comments')}">
82 <a href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id,anchor='comment-%s' % c.comments[cs.raw_id][0].comment_id)}">
82 <a href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id,anchor='comment-%s' % c.comments[cs.raw_id][0].comment_id)}">
83 <div class="comments-cnt">${len(c.comments[cs.raw_id])}</div>
83 <div class="comments-cnt">${len(c.comments[cs.raw_id])}</div>
84 <img src="${h.url('/images/icons/comments.png')}">
84 <img src="${h.url('/images/icons/comments.png')}">
85 </a>
85 </a>
86 </div>
86 </div>
87 %endif
87 %endif
88 </div>
88 </div>
89 <div class="changeset-status-container">
89 <div class="changeset-status-container">
90 %if c.statuses.get(cs.raw_id):
90 %if c.statuses.get(cs.raw_id):
91 <div title="${_('Changeset status')}" class="changeset-status-lbl">${c.statuses.get(cs.raw_id)[1]}</div>
91 <div title="${_('Changeset status')}" class="changeset-status-lbl">${c.statuses.get(cs.raw_id)[1]}</div>
92 <div class="changeset-status-ico">
92 <div class="changeset-status-ico">
93 %if c.statuses.get(cs.raw_id)[2]:
93 %if c.statuses.get(cs.raw_id)[2]:
94 <a class="tooltip" title="${_('Click to open associated pull request')}" href="${h.url('pullrequest_show',repo_name=c.statuses.get(cs.raw_id)[3],pull_request_id=c.statuses.get(cs.raw_id)[2])}"><img src="${h.url('/images/icons/flag_status_%s.png' % c.statuses.get(cs.raw_id)[0])}" /></a>
94 <a class="tooltip" title="${_('Click to open associated pull request')}" href="${h.url('pullrequest_show',repo_name=c.statuses.get(cs.raw_id)[3],pull_request_id=c.statuses.get(cs.raw_id)[2])}"><img src="${h.url('/images/icons/flag_status_%s.png' % c.statuses.get(cs.raw_id)[0])}" /></a>
95 %else:
95 %else:
96 <img src="${h.url('/images/icons/flag_status_%s.png' % c.statuses.get(cs.raw_id)[0])}" />
96 <img src="${h.url('/images/icons/flag_status_%s.png' % c.statuses.get(cs.raw_id)[0])}" />
97 %endif
97 %endif
98 </div>
98 </div>
99 %endif
99 %endif
100 </div>
100 </div>
101 </div>
101 </div>
102 %if cs.parents:
102 %if cs.parents:
103 %for p_cs in reversed(cs.parents):
103 %for p_cs in reversed(cs.parents):
104 <div class="parent">${_('Parent')}
104 <div class="parent">${_('Parent')}
105 <span class="changeset_id">${p_cs.revision}:<span class="changeset_hash">${h.link_to(h.short_id(p_cs.raw_id),
105 <span class="changeset_id">${p_cs.revision}:<span class="changeset_hash">${h.link_to(h.short_id(p_cs.raw_id),
106 h.url('changeset_home',repo_name=c.repo_name,revision=p_cs.raw_id),title=p_cs.message)}</span></span>
106 h.url('changeset_home',repo_name=c.repo_name,revision=p_cs.raw_id),title=p_cs.message)}</span></span>
107 </div>
107 </div>
108 %endfor
108 %endfor
109 %else:
109 %else:
110 <div class="parent">${_('No parents')}</div>
110 <div class="parent">${_('No parents')}</div>
111 %endif
111 %endif
112
112
113 <span class="logtags">
113 <span class="logtags">
114 %if len(cs.parents)>1:
114 %if len(cs.parents)>1:
115 <span class="merge">${_('merge')}</span>
115 <span class="merge">${_('merge')}</span>
116 %endif
116 %endif
117 %if cs.branch:
117 %if cs.branch:
118 <span class="branchtag" title="${'%s %s' % (_('branch'),cs.branch)}">
118 <span class="branchtag" title="${'%s %s' % (_('branch'),cs.branch)}">
119 ${h.link_to(h.shorter(cs.branch),h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}
119 ${h.link_to(h.shorter(cs.branch),h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}
120 </span>
120 </span>
121 %endif
121 %endif
122 %if h.is_hg(c.rhodecode_repo):
122 %if h.is_hg(c.rhodecode_repo):
123 %for book in cs.bookmarks:
123 %for book in cs.bookmarks:
124 <span class="bookbook" title="${'%s %s' % (_('bookmark'),book)}">
124 <span class="bookbook" title="${'%s %s' % (_('bookmark'),book)}">
125 ${h.link_to(h.shorter(book),h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}
125 ${h.link_to(h.shorter(book),h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}
126 </span>
126 </span>
127 %endfor
127 %endfor
128 %endif
128 %endif
129 %for tag in cs.tags:
129 %for tag in cs.tags:
130 <span class="tagtag" title="${'%s %s' % (_('tag'),tag)}">
130 <span class="tagtag" title="${'%s %s' % (_('tag'),tag)}">
131 ${h.link_to(h.shorter(tag),h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}</span>
131 ${h.link_to(h.shorter(tag),h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}</span>
132 %endfor
132 %endfor
133 </span>
133 </span>
134 </div>
134 </div>
135 </div>
135 </div>
136
136
137 %endfor
137 %endfor
138 <div class="pagination-wh pagination-left">
138 <div class="pagination-wh pagination-left">
139 ${c.pagination.pager('$link_previous ~2~ $link_next')}
139 ${c.pagination.pager('$link_previous ~2~ $link_next')}
140 </div>
140 </div>
141 </div>
141 </div>
142 </div>
142 </div>
143
143
144 <script type="text/javascript" src="${h.url('/js/graph.js')}"></script>
144 <script type="text/javascript" src="${h.url('/js/graph.js')}"></script>
145 <script type="text/javascript">
145 <script type="text/javascript">
146 YAHOO.util.Event.onDOMReady(function(){
146 YAHOO.util.Event.onDOMReady(function(){
147
147
148 //Monitor range checkboxes and build a link to changesets
148 //Monitor range checkboxes and build a link to changesets
149 //ranges
149 //ranges
150 var checkboxes = YUD.getElementsByClassName('changeset_range');
150 var checkboxes = YUD.getElementsByClassName('changeset_range');
151 var url_tmpl = "${h.url('changeset_home',repo_name=c.repo_name,revision='__REVRANGE__')}";
151 var url_tmpl = "${h.url('changeset_home',repo_name=c.repo_name,revision='__REVRANGE__')}";
152 YUE.on(checkboxes,'click',function(e){
152 YUE.on(checkboxes,'click',function(e){
153 var clicked_cb = e.currentTarget;
153 var clicked_cb = e.currentTarget;
154 var checked_checkboxes = [];
154 var checked_checkboxes = [];
155 for (pos in checkboxes){
155 for (pos in checkboxes){
156 if(checkboxes[pos].checked){
156 if(checkboxes[pos].checked){
157 checked_checkboxes.push(checkboxes[pos]);
157 checked_checkboxes.push(checkboxes[pos]);
158 }
158 }
159 }
159 }
160
160 if(YUD.get('open_new_pr')){
161 if(checked_checkboxes.length>0){
161 if(checked_checkboxes.length>0){
162 // modify open pull request to show we have selected cs
162 // modify open pull request to show we have selected cs
163 YUD.get('open_new_pr').innerHTML = _TM['Open new pull request for selected changesets'];
163 YUD.get('open_new_pr').innerHTML = _TM['Open new pull request for selected changesets'];
164
164
165 }else{
165 }else{
166 YUD.get('open_new_pr').innerHTML = _TM['Open new pull request'];
166 YUD.get('open_new_pr').innerHTML = _TM['Open new pull request'];
167 }
167 }
168 }
168
169
169 if(checked_checkboxes.length>1){
170 if(checked_checkboxes.length>1){
170 var rev_end = checked_checkboxes[0].name;
171 var rev_end = checked_checkboxes[0].name;
171 var rev_start = checked_checkboxes[checked_checkboxes.length-1].name;
172 var rev_start = checked_checkboxes[checked_checkboxes.length-1].name;
172
173
173 // now select all checkboxes in the middle.
174 // now select all checkboxes in the middle.
174 var checked = false;
175 var checked = false;
175 for (var i=0; i<checkboxes.length; i++){
176 for (var i=0; i<checkboxes.length; i++){
176 var cb = checkboxes[i];
177 var cb = checkboxes[i];
177 var rev = cb.name;
178 var rev = cb.name;
178
179
179 if (rev == rev_end){
180 if (rev == rev_end){
180 checked = true;
181 checked = true;
181 }
182 }
182 if (checked){
183 if (checked){
183 cb.checked = true;
184 cb.checked = true;
184 }
185 }
185 else{
186 else{
186 cb.checked = false;
187 cb.checked = false;
187 }
188 }
188 if (rev == rev_start){
189 if (rev == rev_start){
189 checked = false;
190 checked = false;
190 }
191 }
191
192
192 }
193 }
193
194
194 var url = url_tmpl.replace('__REVRANGE__',
195 var url = url_tmpl.replace('__REVRANGE__',
195 rev_start+'...'+rev_end);
196 rev_start+'...'+rev_end);
196
197
197 var link = _TM['Show selected changes __S -> __E'];
198 var link = _TM['Show selected changes __S -> __E'];
198 link = link.replace('__S',rev_start.substr(0,6));
199 link = link.replace('__S',rev_start.substr(0,6));
199 link = link.replace('__E',rev_end.substr(0,6));
200 link = link.replace('__E',rev_end.substr(0,6));
200 YUD.get('rev_range_container').href = url;
201 YUD.get('rev_range_container').href = url;
201 YUD.get('rev_range_container').innerHTML = link;
202 YUD.get('rev_range_container').innerHTML = link;
202 YUD.setStyle('rev_range_container','display','');
203 YUD.setStyle('rev_range_container','display','');
203 YUD.setStyle('rev_range_clear','display','');
204 YUD.setStyle('rev_range_clear','display','');
204
205
205 }
206 }
206 else{
207 else{
207 YUD.setStyle('rev_range_container','display','none');
208 YUD.setStyle('rev_range_container','display','none');
208 YUD.setStyle('rev_range_clear','display','none');
209 YUD.setStyle('rev_range_clear','display','none');
209 }
210 }
210 });
211 });
211 YUE.on('rev_range_clear','click',function(e){
212 YUE.on('rev_range_clear','click',function(e){
212 for (var i=0; i<checkboxes.length; i++){
213 for (var i=0; i<checkboxes.length; i++){
213 var cb = checkboxes[i];
214 var cb = checkboxes[i];
214 cb.checked = false;
215 cb.checked = false;
215 }
216 }
216 YUE.preventDefault(e);
217 YUE.preventDefault(e);
217 })
218 })
218 var msgs = YUQ('.message');
219 var msgs = YUQ('.message');
219 // get first element height
220 // get first element height
220 var el = YUQ('#graph_content .container')[0];
221 var el = YUQ('#graph_content .container')[0];
221 var row_h = el.clientHeight;
222 var row_h = el.clientHeight;
222 for(var i=0;i<msgs.length;i++){
223 for(var i=0;i<msgs.length;i++){
223 var m = msgs[i];
224 var m = msgs[i];
224
225
225 var h = m.clientHeight;
226 var h = m.clientHeight;
226 var pad = YUD.getStyle(m,'padding');
227 var pad = YUD.getStyle(m,'padding');
227 if(h > row_h){
228 if(h > row_h){
228 var offset = row_h - (h+12);
229 var offset = row_h - (h+12);
229 YUD.setStyle(m.nextElementSibling,'display','block');
230 YUD.setStyle(m.nextElementSibling,'display','block');
230 YUD.setStyle(m.nextElementSibling,'margin-top',offset+'px');
231 YUD.setStyle(m.nextElementSibling,'margin-top',offset+'px');
231 };
232 };
232 }
233 }
233 YUE.on(YUQ('.expand'),'click',function(e){
234 YUE.on(YUQ('.expand'),'click',function(e){
234 var elem = e.currentTarget.parentNode.parentNode;
235 var elem = e.currentTarget.parentNode.parentNode;
235 YUD.setStyle(e.currentTarget,'display','none');
236 YUD.setStyle(e.currentTarget,'display','none');
236 YUD.setStyle(elem,'height','auto');
237 YUD.setStyle(elem,'height','auto');
237
238
238 //redraw the graph, line_count and jsdata are global vars
239 //redraw the graph, line_count and jsdata are global vars
239 set_canvas(100);
240 set_canvas(100);
240
241
241 var r = new BranchRenderer();
242 var r = new BranchRenderer();
242 r.render(jsdata,100,line_count);
243 r.render(jsdata,100,line_count);
243
244
244 })
245 })
245
246
246 // Fetch changeset details
247 // Fetch changeset details
247 YUE.on(YUD.getElementsByClassName('changed_total'),'click',function(e){
248 YUE.on(YUD.getElementsByClassName('changed_total'),'click',function(e){
248 var id = e.currentTarget.id;
249 var id = e.currentTarget.id;
249 var url = "${h.url('changelog_details',repo_name=c.repo_name,cs='__CS__')}";
250 var url = "${h.url('changelog_details',repo_name=c.repo_name,cs='__CS__')}";
250 var url = url.replace('__CS__',id.replace('changed_total_',''));
251 var url = url.replace('__CS__',id.replace('changed_total_',''));
251 ypjax(url,id,function(){tooltip_activate()});
252 ypjax(url,id,function(){tooltip_activate()});
252 });
253 });
253
254
254 // change branch filter
255 // change branch filter
255 YUE.on(YUD.get('branch_filter'),'change',function(e){
256 YUE.on(YUD.get('branch_filter'),'change',function(e){
256 var selected_branch = e.currentTarget.options[e.currentTarget.selectedIndex].value;
257 var selected_branch = e.currentTarget.options[e.currentTarget.selectedIndex].value;
257 var url_main = "${h.url('changelog_home',repo_name=c.repo_name)}";
258 var url_main = "${h.url('changelog_home',repo_name=c.repo_name)}";
258 var url = "${h.url('changelog_home',repo_name=c.repo_name,branch='__BRANCH__')}";
259 var url = "${h.url('changelog_home',repo_name=c.repo_name,branch='__BRANCH__')}";
259 var url = url.replace('__BRANCH__',selected_branch);
260 var url = url.replace('__BRANCH__',selected_branch);
260 if(selected_branch != ''){
261 if(selected_branch != ''){
261 window.location = url;
262 window.location = url;
262 }else{
263 }else{
263 window.location = url_main;
264 window.location = url_main;
264 }
265 }
265
266
266 });
267 });
267
268
268 function set_canvas(width) {
269 function set_canvas(width) {
269 var c = document.getElementById('graph_nodes');
270 var c = document.getElementById('graph_nodes');
270 var t = document.getElementById('graph_content');
271 var t = document.getElementById('graph_content');
271 canvas = document.getElementById('graph_canvas');
272 canvas = document.getElementById('graph_canvas');
272 var div_h = t.clientHeight;
273 var div_h = t.clientHeight;
273 c.style.height=div_h+'px';
274 c.style.height=div_h+'px';
274 canvas.setAttribute('height',div_h);
275 canvas.setAttribute('height',div_h);
275 c.style.height=width+'px';
276 c.style.height=width+'px';
276 canvas.setAttribute('width',width);
277 canvas.setAttribute('width',width);
277 };
278 };
278 var heads = 1;
279 var heads = 1;
279 var line_count = 0;
280 var line_count = 0;
280 var jsdata = ${c.jsdata|n};
281 var jsdata = ${c.jsdata|n};
281
282
282 for (var i=0;i<jsdata.length;i++) {
283 for (var i=0;i<jsdata.length;i++) {
283 var in_l = jsdata[i][2];
284 var in_l = jsdata[i][2];
284 for (var j in in_l) {
285 for (var j in in_l) {
285 var m = in_l[j][1];
286 var m = in_l[j][1];
286 if (m > line_count)
287 if (m > line_count)
287 line_count = m;
288 line_count = m;
288 }
289 }
289 }
290 }
290 set_canvas(100);
291 set_canvas(100);
291
292
292 var r = new BranchRenderer();
293 var r = new BranchRenderer();
293 r.render(jsdata,100,line_count);
294 r.render(jsdata,100,line_count);
294
295
295 });
296 });
296 </script>
297 </script>
297 %else:
298 %else:
298 ${_('There are no changes yet')}
299 ${_('There are no changes yet')}
299 %endif
300 %endif
300 </div>
301 </div>
301 </div>
302 </div>
302 </%def>
303 </%def>
@@ -1,190 +1,191 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2
2
3 <%inherit file="/base/base.html"/>
3 <%inherit file="/base/base.html"/>
4
4
5 <%def name="title()">
5 <%def name="title()">
6 ${_('%s Changeset') % c.repo_name} - r${c.changeset.revision}:${h.short_id(c.changeset.raw_id)} - ${c.rhodecode_name}
6 ${_('%s Changeset') % c.repo_name} - r${c.changeset.revision}:${h.short_id(c.changeset.raw_id)} - ${c.rhodecode_name}
7 </%def>
7 </%def>
8
8
9 <%def name="breadcrumbs_links()">
9 <%def name="breadcrumbs_links()">
10 ${h.link_to(_(u'Home'),h.url('/'))}
10 ${h.link_to(_(u'Home'),h.url('/'))}
11 &raquo;
11 &raquo;
12 ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
12 ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
13 &raquo;
13 &raquo;
14 ${_('Changeset')} - <span class='hash'>r${c.changeset.revision}:${h.short_id(c.changeset.raw_id)}</span>
14 ${_('Changeset')} - <span class='hash'>r${c.changeset.revision}:${h.short_id(c.changeset.raw_id)}</span>
15 </%def>
15 </%def>
16
16
17 <%def name="page_nav()">
17 <%def name="page_nav()">
18 ${self.menu('changelog')}
18 ${self.menu('changelog')}
19 </%def>
19 </%def>
20
20
21 <%def name="main()">
21 <%def name="main()">
22 <div class="box">
22 <div class="box">
23 <!-- box / title -->
23 <!-- box / title -->
24 <div class="title">
24 <div class="title">
25 ${self.breadcrumbs()}
25 ${self.breadcrumbs()}
26 </div>
26 </div>
27 <script>
27 <script>
28 var _USERS_AC_DATA = ${c.users_array|n};
28 var _USERS_AC_DATA = ${c.users_array|n};
29 var _GROUPS_AC_DATA = ${c.users_groups_array|n};
29 var _GROUPS_AC_DATA = ${c.users_groups_array|n};
30 AJAX_COMMENT_URL = "${url('changeset_comment',repo_name=c.repo_name,revision=c.changeset.raw_id)}";
30 AJAX_COMMENT_URL = "${url('changeset_comment',repo_name=c.repo_name,revision=c.changeset.raw_id)}";
31 AJAX_COMMENT_DELETE_URL = "${url('changeset_comment_delete',repo_name=c.repo_name,comment_id='__COMMENT_ID__')}";
31 AJAX_COMMENT_DELETE_URL = "${url('changeset_comment_delete',repo_name=c.repo_name,comment_id='__COMMENT_ID__')}";
32 </script>
32 </script>
33 <div class="table">
33 <div class="table">
34 <div class="diffblock">
34 <div class="diffblock">
35 <div class="code-header">
35 <div class="code-header">
36 <div class="hash">
36 <div class="hash">
37 r${c.changeset.revision}:${h.short_id(c.changeset.raw_id)}
37 r${c.changeset.revision}:${h.short_id(c.changeset.raw_id)}
38 </div>
38 </div>
39 <div class="date">
39 <div class="date">
40 ${h.fmt_date(c.changeset.date)}
40 ${h.fmt_date(c.changeset.date)}
41 </div>
41 </div>
42 <div class="changeset-status-container">
42 <div class="changeset-status-container">
43 %if c.statuses:
43 %if c.statuses:
44 <div title="${_('Changeset status')}" class="changeset-status-lbl">[${h.changeset_status_lbl(c.statuses[0])}]</div>
44 <div title="${_('Changeset status')}" class="changeset-status-lbl">[${h.changeset_status_lbl(c.statuses[0])}]</div>
45 <div class="changeset-status-ico"><img src="${h.url('/images/icons/flag_status_%s.png' % c.statuses[0])}" /></div>
45 <div class="changeset-status-ico"><img src="${h.url('/images/icons/flag_status_%s.png' % c.statuses[0])}" /></div>
46 %endif
46 %endif
47 </div>
47 </div>
48 <div class="diff-actions">
48 <div class="diff-actions">
49 <a href="${h.url('raw_changeset_home',repo_name=c.repo_name,revision=c.changeset.raw_id,diff='raw')}" class="tooltip" title="${h.tooltip(_('raw diff'))}"><img class="icon" src="${h.url('/images/icons/page_white.png')}"/></a>
49 <a href="${h.url('changeset_raw_home',repo_name=c.repo_name,revision=c.changeset.raw_id)}" class="tooltip" title="${h.tooltip(_('raw diff'))}"><img class="icon" src="${h.url('/images/icons/page_white.png')}"/></a>
50 <a href="${h.url('raw_changeset_home',repo_name=c.repo_name,revision=c.changeset.raw_id,diff='download')}" class="tooltip" title="${h.tooltip(_('download diff'))}"><img class="icon" src="${h.url('/images/icons/page_white_get.png')}"/></a>
50 <a href="${h.url('changeset_patch_home',repo_name=c.repo_name,revision=c.changeset.raw_id)}" class="tooltip" title="${h.tooltip(_('patch diff'))}"><img class="icon" src="${h.url('/images/icons/page_add.png')}"/></a>
51 <a href="${h.url('changeset_download_home',repo_name=c.repo_name,revision=c.changeset.raw_id,diff='download')}" class="tooltip" title="${h.tooltip(_('download diff'))}"><img class="icon" src="${h.url('/images/icons/page_save.png')}"/></a>
51 ${c.ignorews_url(request.GET)}
52 ${c.ignorews_url(request.GET)}
52 ${c.context_url(request.GET)}
53 ${c.context_url(request.GET)}
53 </div>
54 </div>
54 <div class="comments-number" style="float:right;padding-right:5px">${ungettext("%d comment", "%d comments", len(c.comments)) % len(c.comments)} ${ungettext("(%d inline)", "(%d inline)", c.inline_cnt) % c.inline_cnt}</div>
55 <div class="comments-number" style="float:right;padding-right:5px">${ungettext("%d comment", "%d comments", len(c.comments)) % len(c.comments)} ${ungettext("(%d inline)", "(%d inline)", c.inline_cnt) % c.inline_cnt}</div>
55 </div>
56 </div>
56 </div>
57 </div>
57 <div id="changeset_content">
58 <div id="changeset_content">
58 <div class="container">
59 <div class="container">
59 <div class="left">
60 <div class="left">
60 <div class="author">
61 <div class="author">
61 <div class="gravatar">
62 <div class="gravatar">
62 <img alt="gravatar" src="${h.gravatar_url(h.email_or_none(c.changeset.author),20)}"/>
63 <img alt="gravatar" src="${h.gravatar_url(h.email_or_none(c.changeset.author),20)}"/>
63 </div>
64 </div>
64 <span>${h.person(c.changeset.author)}</span><br/>
65 <span>${h.person(c.changeset.author)}</span><br/>
65 <span><a href="mailto:${h.email_or_none(c.changeset.author)}">${h.email_or_none(c.changeset.author)}</a></span><br/>
66 <span><a href="mailto:${h.email_or_none(c.changeset.author)}">${h.email_or_none(c.changeset.author)}</a></span><br/>
66 </div>
67 </div>
67 <div class="message">${h.urlify_commit(c.changeset.message, c.repo_name)}</div>
68 <div class="message">${h.urlify_commit(c.changeset.message, c.repo_name)}</div>
68 </div>
69 </div>
69 <div class="right">
70 <div class="right">
70 <div class="changes">
71 <div class="changes">
71 % if len(c.changeset.affected_files) <= c.affected_files_cut_off:
72 % if (len(c.changeset.affected_files) <= c.affected_files_cut_off) or c.fulldiff:
72 <span class="removed" title="${_('removed')}">${len(c.changeset.removed)}</span>
73 <span class="removed" title="${_('removed')}">${len(c.changeset.removed)}</span>
73 <span class="changed" title="${_('changed')}">${len(c.changeset.changed)}</span>
74 <span class="changed" title="${_('changed')}">${len(c.changeset.changed)}</span>
74 <span class="added" title="${_('added')}">${len(c.changeset.added)}</span>
75 <span class="added" title="${_('added')}">${len(c.changeset.added)}</span>
75 % else:
76 % else:
76 <span class="removed" title="${_('affected %s files') % len(c.changeset.affected_files)}">!</span>
77 <span class="removed" title="${_('affected %s files') % len(c.changeset.affected_files)}">!</span>
77 <span class="changed" title="${_('affected %s files') % len(c.changeset.affected_files)}">!</span>
78 <span class="changed" title="${_('affected %s files') % len(c.changeset.affected_files)}">!</span>
78 <span class="added" title="${_('affected %s files') % len(c.changeset.affected_files)}">!</span>
79 <span class="added" title="${_('affected %s files') % len(c.changeset.affected_files)}">!</span>
79 % endif
80 % endif
80 </div>
81 </div>
81
82
82 %if c.changeset.parents:
83 %if c.changeset.parents:
83 %for p_cs in reversed(c.changeset.parents):
84 %for p_cs in reversed(c.changeset.parents):
84 <div class="parent">${_('Parent')}
85 <div class="parent">${_('Parent')}
85 <span class="changeset_id">${p_cs.revision}:<span class="changeset_hash">${h.link_to(h.short_id(p_cs.raw_id),
86 <span class="changeset_id">${p_cs.revision}:<span class="changeset_hash">${h.link_to(h.short_id(p_cs.raw_id),
86 h.url('changeset_home',repo_name=c.repo_name,revision=p_cs.raw_id),title=p_cs.message)}</span></span>
87 h.url('changeset_home',repo_name=c.repo_name,revision=p_cs.raw_id),title=p_cs.message)}</span></span>
87 </div>
88 </div>
88 %endfor
89 %endfor
89 %else:
90 %else:
90 <div class="parent">${_('No parents')}</div>
91 <div class="parent">${_('No parents')}</div>
91 %endif
92 %endif
92 <span class="logtags">
93 <span class="logtags">
93 %if len(c.changeset.parents)>1:
94 %if len(c.changeset.parents)>1:
94 <span class="merge">${_('merge')}</span>
95 <span class="merge">${_('merge')}</span>
95 %endif
96 %endif
96 %if c.changeset.branch:
97 %if c.changeset.branch:
97 <span class="branchtag" title="${'%s %s' % (_('branch'),c.changeset.branch)}">
98 <span class="branchtag" title="${'%s %s' % (_('branch'),c.changeset.branch)}">
98 ${h.link_to(c.changeset.branch,h.url('files_home',repo_name=c.repo_name,revision=c.changeset.raw_id))}
99 ${h.link_to(c.changeset.branch,h.url('files_home',repo_name=c.repo_name,revision=c.changeset.raw_id))}
99 </span>
100 </span>
100 %endif
101 %endif
101 %for tag in c.changeset.tags:
102 %for tag in c.changeset.tags:
102 <span class="tagtag" title="${'%s %s' % (_('tag'),tag)}">
103 <span class="tagtag" title="${'%s %s' % (_('tag'),tag)}">
103 ${h.link_to(tag,h.url('files_home',repo_name=c.repo_name,revision=c.changeset.raw_id))}</span>
104 ${h.link_to(tag,h.url('files_home',repo_name=c.repo_name,revision=c.changeset.raw_id))}</span>
104 %endfor
105 %endfor
105 </span>
106 </span>
106 </div>
107 </div>
107 </div>
108 </div>
108 <span>
109 <span>
109 % if c.limited_diff:
110 % if c.limited_diff:
110 ${_('%s files affected:') % (len(c.changeset.affected_files))}
111 ${_('%s files affected:') % (len(c.changeset.affected_files))}
111 % else:
112 % else:
112 ${_('%s files affected with %s insertions and %s deletions:') % (len(c.changeset.affected_files),c.lines_added,c.lines_deleted)}
113 ${_('%s files affected with %s insertions and %s deletions:') % (len(c.changeset.affected_files),c.lines_added,c.lines_deleted)}
113 %endif
114 %endif
114 </span>
115 </span>
115 <div class="cs_files">
116 <div class="cs_files">
116 %for FID, (cs1, cs2, change, path, diff, stats) in c.changes[c.changeset.raw_id].iteritems():
117 %for FID, (cs1, cs2, change, path, diff, stats) in c.changes[c.changeset.raw_id].iteritems():
117 <div class="cs_${change}">
118 <div class="cs_${change}">
118 <div class="node">
119 <div class="node">
119 <a href="#${FID}">${h.safe_unicode(path)}</a>
120 <a href="#${FID}">${h.safe_unicode(path)}</a>
120 </div>
121 </div>
121 <div class="changes">${h.fancy_file_stats(stats)}</div>
122 <div class="changes">${h.fancy_file_stats(stats)}</div>
122 </div>
123 </div>
123 %endfor
124 %endfor
124 % if c.limited_diff:
125 % if c.limited_diff:
125 <h5>${_('Changeset was too big and was cut off...')}</h5>
126 <h5>${_('Changeset was too big and was cut off...')}</h5>
126 % endif
127 % endif
127 </div>
128 </div>
128 </div>
129 </div>
129
130
130 </div>
131 </div>
131
132
132 ## diff block
133 ## diff block
133 <%namespace name="diff_block" file="/changeset/diff_block.html"/>
134 <%namespace name="diff_block" file="/changeset/diff_block.html"/>
134 ${diff_block.diff_block(c.changes[c.changeset.raw_id])}
135 ${diff_block.diff_block(c.changes[c.changeset.raw_id])}
135
136
136 % if c.limited_diff:
137 % if c.limited_diff:
137 <h4>${_('Changeset was too big and was cut off...')}</h4>
138 <h4>${_('Changeset was too big and was cut off...')}</h4>
138 % endif
139 % endif
139
140
140 ## template for inline comment form
141 ## template for inline comment form
141 <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
142 <%namespace name="comment" file="/changeset/changeset_file_comment.html"/>
142 ${comment.comment_inline_form()}
143 ${comment.comment_inline_form()}
143
144
144 ## render comments and inlines
145 ## render comments and inlines
145 ${comment.generate_comments()}
146 ${comment.generate_comments()}
146
147
147 ## main comment form and it status
148 ## main comment form and it status
148 ${comment.comments(h.url('changeset_comment', repo_name=c.repo_name, revision=c.changeset.raw_id),
149 ${comment.comments(h.url('changeset_comment', repo_name=c.repo_name, revision=c.changeset.raw_id),
149 h.changeset_status(c.rhodecode_db_repo, c.changeset.raw_id))}
150 h.changeset_status(c.rhodecode_db_repo, c.changeset.raw_id))}
150
151
151 ## FORM FOR MAKING JS ACTION AS CHANGESET COMMENTS
152 ## FORM FOR MAKING JS ACTION AS CHANGESET COMMENTS
152 <script type="text/javascript">
153 <script type="text/javascript">
153 YUE.onDOMReady(function(){
154 YUE.onDOMReady(function(){
154 YUE.on(YUQ('.show-inline-comments'),'change',function(e){
155 YUE.on(YUQ('.show-inline-comments'),'change',function(e){
155 var show = 'none';
156 var show = 'none';
156 var target = e.currentTarget;
157 var target = e.currentTarget;
157 if(target == null){
158 if(target == null){
158 target = this;
159 target = this;
159 }
160 }
160 if(target.checked){
161 if(target.checked){
161 var show = ''
162 var show = ''
162 }
163 }
163 var boxid = YUD.getAttribute(target,'id_for');
164 var boxid = YUD.getAttribute(target,'id_for');
164 var comments = YUQ('#{0} .inline-comments'.format(boxid));
165 var comments = YUQ('#{0} .inline-comments'.format(boxid));
165 for(c in comments){
166 for(c in comments){
166 YUD.setStyle(comments[c],'display',show);
167 YUD.setStyle(comments[c],'display',show);
167 }
168 }
168 var btns = YUQ('#{0} .inline-comments-button'.format(boxid));
169 var btns = YUQ('#{0} .inline-comments-button'.format(boxid));
169 for(c in btns){
170 for(c in btns){
170 YUD.setStyle(btns[c],'display',show);
171 YUD.setStyle(btns[c],'display',show);
171 }
172 }
172 })
173 })
173
174
174 YUE.on(YUQ('.line'),'click',function(e){
175 YUE.on(YUQ('.line'),'click',function(e){
175 var tr = e.currentTarget;
176 var tr = e.currentTarget;
176 if(tr == null){
177 if(tr == null){
177 tr = this;
178 tr = this;
178 }
179 }
179 injectInlineForm(tr);
180 injectInlineForm(tr);
180 });
181 });
181
182
182 // inject comments into they proper positions
183 // inject comments into they proper positions
183 var file_comments = YUQ('.inline-comment-placeholder');
184 var file_comments = YUQ('.inline-comment-placeholder');
184 renderInlineComments(file_comments);
185 renderInlineComments(file_comments);
185 })
186 })
186
187
187 </script>
188 </script>
188
189
189 </div>
190 </div>
190 </%def>
191 </%def>
@@ -1,61 +1,61 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 ##usage:
2 ##usage:
3 ## <%namespace name="diff_block" file="/changeset/diff_block.html"/>
3 ## <%namespace name="diff_block" file="/changeset/diff_block.html"/>
4 ## ${diff_block.diff_block(change)}
4 ## ${diff_block.diff_block(change)}
5 ##
5 ##
6 <%def name="diff_block(change)">
6 <%def name="diff_block(change)">
7
7
8 %for FID,(cs1, cs2, change, path, diff, stats) in change.iteritems():
8 %for FID,(cs1, cs2, change, path, diff, stats) in change.iteritems():
9 ##%if op !='removed':
9 ##%if op !='removed':
10 <div id="${FID}_target" style="clear:both;margin-top:25px"></div>
10 <div id="${FID}_target" style="clear:both;margin-top:25px"></div>
11 <div id="${FID}" class="diffblock margined comm">
11 <div id="${FID}" class="diffblock margined comm">
12 <div class="code-header">
12 <div class="code-header">
13 <div class="changeset_header">
13 <div class="changeset_header">
14 <div class="changeset_file">
14 <div class="changeset_file">
15 ${h.link_to_if(change!='removed',h.safe_unicode(path),h.url('files_home',repo_name=c.repo_name,
15 ${h.link_to_if(change!='removed',h.safe_unicode(path),h.url('files_home',repo_name=c.repo_name,
16 revision=cs2,f_path=h.safe_unicode(path)))}
16 revision=cs2,f_path=h.safe_unicode(path)))}
17 </div>
17 </div>
18 <div class="diff-actions">
18 <div class="diff-actions">
19 <a href="${h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(path),diff2=cs2,diff1=cs1,diff='diff',fulldiff=1)}" class="tooltip" title="${h.tooltip(_('diff'))}"><img class="icon" src="${h.url('/images/icons/page_white_go.png')}"/></a>
19 <a href="${h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(path),diff2=cs2,diff1=cs1,diff='diff',fulldiff=1)}" class="tooltip" title="${h.tooltip(_('show full diff for this file'))}"><img class="icon" src="${h.url('/images/icons/page_white_go.png')}"/></a>
20 <a href="${h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(path),diff2=cs2,diff1=cs1,diff='raw')}" class="tooltip" title="${h.tooltip(_('raw diff'))}"><img class="icon" src="${h.url('/images/icons/page_white.png')}"/></a>
20 <a href="${h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(path),diff2=cs2,diff1=cs1,diff='raw')}" class="tooltip" title="${h.tooltip(_('raw diff'))}"><img class="icon" src="${h.url('/images/icons/page_white.png')}"/></a>
21 <a href="${h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(path),diff2=cs2,diff1=cs1,diff='download')}" class="tooltip" title="${h.tooltip(_('download diff'))}"><img class="icon" src="${h.url('/images/icons/page_white_get.png')}"/></a>
21 <a href="${h.url('files_diff_home',repo_name=c.repo_name,f_path=h.safe_unicode(path),diff2=cs2,diff1=cs1,diff='download')}" class="tooltip" title="${h.tooltip(_('download diff'))}"><img class="icon" src="${h.url('/images/icons/page_save.png')}"/></a>
22 ${c.ignorews_url(request.GET, h.FID(cs2,path))}
22 ${c.ignorews_url(request.GET, h.FID(cs2,path))}
23 ${c.context_url(request.GET, h.FID(cs2,path))}
23 ${c.context_url(request.GET, h.FID(cs2,path))}
24 </div>
24 </div>
25 <span style="float:right;margin-top:-3px">
25 <span style="float:right;margin-top:-3px">
26 <label>
26 <label>
27 ${_('show inline comments')}
27 ${_('show inline comments')}
28 ${h.checkbox('',checked="checked",class_="show-inline-comments",id_for=h.FID(cs2,path))}
28 ${h.checkbox('',checked="checked",class_="show-inline-comments",id_for=h.FID(cs2,path))}
29 </label>
29 </label>
30 </span>
30 </span>
31 </div>
31 </div>
32 </div>
32 </div>
33 <div class="code-body">
33 <div class="code-body">
34 <div class="full_f_path" path="${h.safe_unicode(path)}"></div>
34 <div class="full_f_path" path="${h.safe_unicode(path)}"></div>
35 ${diff|n}
35 ${diff|n}
36 </div>
36 </div>
37 </div>
37 </div>
38 ##%endif
38 ##%endif
39 %endfor
39 %endfor
40
40
41 </%def>
41 </%def>
42
42
43 <%def name="diff_block_simple(change)">
43 <%def name="diff_block_simple(change)">
44
44
45 %for op,filenode_path,diff in change:
45 %for op,filenode_path,diff in change:
46 <div id="${h.FID('',filenode_path)}_target" style="clear:both;margin-top:25px"></div>
46 <div id="${h.FID('',filenode_path)}_target" style="clear:both;margin-top:25px"></div>
47 <div id="${h.FID('',filenode_path)}" class="diffblock margined comm">
47 <div id="${h.FID('',filenode_path)}" class="diffblock margined comm">
48 <div class="code-header">
48 <div class="code-header">
49 <div class="changeset_header">
49 <div class="changeset_header">
50 <div class="changeset_file">
50 <div class="changeset_file">
51 <a href="#">${h.safe_unicode(filenode_path)}</a>
51 <a href="#">${h.safe_unicode(filenode_path)}</a>
52 </div>
52 </div>
53 </div>
53 </div>
54 </div>
54 </div>
55 <div class="code-body">
55 <div class="code-body">
56 <div class="full_f_path" path="${h.safe_unicode(filenode_path)}"></div>
56 <div class="full_f_path" path="${h.safe_unicode(filenode_path)}"></div>
57 ${diff|n}
57 ${diff|n}
58 </div>
58 </div>
59 </div>
59 </div>
60 %endfor
60 %endfor
61 </%def>
61 </%def>
@@ -1,11 +1,17 b''
1 %if h.is_hg(c.rhodecode_repo):
1 %if h.is_hg(c.rhodecode_repo):
2 # ${c.rhodecode_repo.alias.upper()} changeset patch
2 # ${c.rhodecode_repo.alias.upper()} changeset patch
3 # User ${c.changeset.author|n}
3 # User ${c.changeset.author|n}
4 # Date ${c.changeset.date}
4 # Date ${c.changeset.date}
5 # Node ID ${c.changeset.raw_id}
5 # Node ID ${c.changeset.raw_id}
6 ${c.parent_tmpl}
6 ${c.parent_tmpl}
7 ${c.changeset.message}
7 ${c.changeset.message}
8
9 %elif h.is_git(c.rhodecode_repo):
10 From 35d9475598be9f807cd800e51212b8f1efbeacd9 Mon Sep 17 00:00:00 2001
11 From: ${c.changeset.author}
12 Date: ${c.changeset.date}
13 Subject: [PATCH] ${c.changeset.message}
14 ---
15
8 %endif
16 %endif
9 %for FID, (cs1, cs2, change, path, diff, stats) in c.changes[c.changeset.raw_id].iteritems():
17 ${c.diff|n}
10 ${diff|n}
11 %endfor
General Comments 0
You need to be logged in to leave comments. Login now