##// END OF EJS Templates
fixes #550 mercurial repositories comparision failed when origin repo had...
marcink -
r2801:69420c48 beta
parent child Browse files
Show More
@@ -1,740 +1,741 b''
1 .. _changelog:
1 .. _changelog:
2
2
3 =========
3 =========
4 Changelog
4 Changelog
5 =========
5 =========
6
6
7
7
8 1.4.1 (**2012-09-04**)
8 1.4.1 (**2012-09-04**)
9 ----------------------
9 ----------------------
10
10
11 :status: in-progress
11 :status: in-progress
12 :branch: beta
12 :branch: beta
13
13
14 news
14 news
15 ++++
15 ++++
16
16
17 - always put a comment about code-review status change even if user send
17 - always put a comment about code-review status change even if user send
18 empty data
18 empty data
19 - modified_on column saves repository update and it's going to be used
19 - modified_on column saves repository update and it's going to be used
20 later for light version of main page ref #500
20 later for light version of main page ref #500
21 - pull request notifications send much nicer emails with details about pull
21 - pull request notifications send much nicer emails with details about pull
22 request
22 request
23
23
24 fixes
24 fixes
25 +++++
25 +++++
26
26
27 - fixed migrations of permissions that can lead to inconsistency.
27 - fixed migrations of permissions that can lead to inconsistency.
28 Some users sent feedback that after upgrading from older versions issues
28 Some users sent feedback that after upgrading from older versions issues
29 with updating default permissions occurred. RhodeCode detects that now and
29 with updating default permissions occurred. RhodeCode detects that now and
30 resets default user permission to initial state if there is a need for that.
30 resets default user permission to initial state if there is a need for that.
31 Also forces users to set the default value for new forking permission.
31 Also forces users to set the default value for new forking permission.
32 - #535 improved apache wsgi example configuration in docs
32 - #535 improved apache wsgi example configuration in docs
33
33 - fixes #550 mercurial repositories comparision failed when origin repo had
34 additional not-common changesets
34
35
35 1.4.0 (**2012-09-03**)
36 1.4.0 (**2012-09-03**)
36 ----------------------
37 ----------------------
37
38
38 news
39 news
39 ++++
40 ++++
40
41
41 - new codereview system
42 - new codereview system
42 - email map, allowing users to have multiple email addresses mapped into
43 - email map, allowing users to have multiple email addresses mapped into
43 their accounts
44 their accounts
44 - improved git-hook system. Now all actions for git are logged into journal
45 - improved git-hook system. Now all actions for git are logged into journal
45 including pushed revisions, user and IP address
46 including pushed revisions, user and IP address
46 - changed setup-app into setup-rhodecode and added default options to it.
47 - changed setup-app into setup-rhodecode and added default options to it.
47 - new git repos are created as bare now by default
48 - new git repos are created as bare now by default
48 - #464 added links to groups in permission box
49 - #464 added links to groups in permission box
49 - #465 mentions autocomplete inside comments boxes
50 - #465 mentions autocomplete inside comments boxes
50 - #469 added --update-only option to whoosh to re-index only given list
51 - #469 added --update-only option to whoosh to re-index only given list
51 of repos in index
52 of repos in index
52 - rhodecode-api CLI client
53 - rhodecode-api CLI client
53 - new git http protocol replaced buggy dulwich implementation.
54 - new git http protocol replaced buggy dulwich implementation.
54 Now based on pygrack & gitweb
55 Now based on pygrack & gitweb
55 - Improved RSS/ATOM feeds. Discoverable by browsers using proper headers, and
56 - Improved RSS/ATOM feeds. Discoverable by browsers using proper headers, and
56 reformated based on user suggestions. Additional rss/atom feeds for user
57 reformated based on user suggestions. Additional rss/atom feeds for user
57 journal
58 journal
58 - various i18n improvements
59 - various i18n improvements
59 - #478 permissions overview for admin in user edit view
60 - #478 permissions overview for admin in user edit view
60 - File view now displays small gravatars off all authors of given file
61 - File view now displays small gravatars off all authors of given file
61 - Implemented landing revisions. Each repository will get landing_rev attribute
62 - Implemented landing revisions. Each repository will get landing_rev attribute
62 that defines 'default' revision/branch for generating readme files
63 that defines 'default' revision/branch for generating readme files
63 - Implemented #509, RhodeCode enforces SSL for push/pulling if requested at
64 - Implemented #509, RhodeCode enforces SSL for push/pulling if requested at
64 earliest possible call.
65 earliest possible call.
65 - Import remote svn repositories to mercurial using hgsubversion.
66 - Import remote svn repositories to mercurial using hgsubversion.
66 - Fixed #508 RhodeCode now has a option to explicitly set forking permissions
67 - Fixed #508 RhodeCode now has a option to explicitly set forking permissions
67 - RhodeCode can use alternative server for generating avatar icons
68 - RhodeCode can use alternative server for generating avatar icons
68 - implemented repositories locking. Pull locks, push unlocks. Also can be done
69 - implemented repositories locking. Pull locks, push unlocks. Also can be done
69 via API calls
70 via API calls
70 - #538 form for permissions can handle multiple users at once
71 - #538 form for permissions can handle multiple users at once
71
72
72 fixes
73 fixes
73 +++++
74 +++++
74
75
75 - improved translations
76 - improved translations
76 - fixes issue #455 Creating an archive generates an exception on Windows
77 - fixes issue #455 Creating an archive generates an exception on Windows
77 - fixes #448 Download ZIP archive keeps file in /tmp open and results
78 - fixes #448 Download ZIP archive keeps file in /tmp open and results
78 in out of disk space
79 in out of disk space
79 - fixes issue #454 Search results under Windows include proceeding
80 - fixes issue #454 Search results under Windows include proceeding
80 backslash
81 backslash
81 - fixed issue #450. Rhodecode no longer will crash when bad revision is
82 - fixed issue #450. Rhodecode no longer will crash when bad revision is
82 present in journal data.
83 present in journal data.
83 - fix for issue #417, git execution was broken on windows for certain
84 - fix for issue #417, git execution was broken on windows for certain
84 commands.
85 commands.
85 - fixed #413. Don't disable .git directory for bare repos on deleting
86 - fixed #413. Don't disable .git directory for bare repos on deleting
86 - fixed issue #459. Changed the way of obtaining logger in reindex task.
87 - fixed issue #459. Changed the way of obtaining logger in reindex task.
87 - fixed #453 added ID field in whoosh SCHEMA that solves the issue of
88 - fixed #453 added ID field in whoosh SCHEMA that solves the issue of
88 reindexing modified files
89 reindexing modified files
89 - fixed #481 rhodecode emails are sent without Date header
90 - fixed #481 rhodecode emails are sent without Date header
90 - fixed #458 wrong count when no repos are present
91 - fixed #458 wrong count when no repos are present
91 - fixed issue #492 missing `\ No newline at end of file` test at the end of
92 - fixed issue #492 missing `\ No newline at end of file` test at the end of
92 new chunk in html diff
93 new chunk in html diff
93 - full text search now works also for commit messages
94 - full text search now works also for commit messages
94
95
95 1.3.6 (**2012-05-17**)
96 1.3.6 (**2012-05-17**)
96 ----------------------
97 ----------------------
97
98
98 news
99 news
99 ++++
100 ++++
100
101
101 - chinese traditional translation
102 - chinese traditional translation
102 - changed setup-app into setup-rhodecode and added arguments for auto-setup
103 - changed setup-app into setup-rhodecode and added arguments for auto-setup
103 mode that doesn't need user interaction
104 mode that doesn't need user interaction
104
105
105 fixes
106 fixes
106 +++++
107 +++++
107
108
108 - fixed no scm found warning
109 - fixed no scm found warning
109 - fixed __future__ import error on rcextensions
110 - fixed __future__ import error on rcextensions
110 - made simplejson required lib for speedup on JSON encoding
111 - made simplejson required lib for speedup on JSON encoding
111 - fixes #449 bad regex could get more than revisions from parsing history
112 - fixes #449 bad regex could get more than revisions from parsing history
112 - don't clear DB session when CELERY_EAGER is turned ON
113 - don't clear DB session when CELERY_EAGER is turned ON
113
114
114 1.3.5 (**2012-05-10**)
115 1.3.5 (**2012-05-10**)
115 ----------------------
116 ----------------------
116
117
117 news
118 news
118 ++++
119 ++++
119
120
120 - use ext_json for json module
121 - use ext_json for json module
121 - unified annotation view with file source view
122 - unified annotation view with file source view
122 - notification improvements, better inbox + css
123 - notification improvements, better inbox + css
123 - #419 don't strip passwords for login forms, make rhodecode
124 - #419 don't strip passwords for login forms, make rhodecode
124 more compatible with LDAP servers
125 more compatible with LDAP servers
125 - Added HTTP_X_FORWARDED_FOR as another method of extracting
126 - Added HTTP_X_FORWARDED_FOR as another method of extracting
126 IP for pull/push logs. - moved all to base controller
127 IP for pull/push logs. - moved all to base controller
127 - #415: Adding comment to changeset causes reload.
128 - #415: Adding comment to changeset causes reload.
128 Comments are now added via ajax and doesn't reload the page
129 Comments are now added via ajax and doesn't reload the page
129 - #374 LDAP config is discarded when LDAP can't be activated
130 - #374 LDAP config is discarded when LDAP can't be activated
130 - limited push/pull operations are now logged for git in the journal
131 - limited push/pull operations are now logged for git in the journal
131 - bumped mercurial to 2.2.X series
132 - bumped mercurial to 2.2.X series
132 - added support for displaying submodules in file-browser
133 - added support for displaying submodules in file-browser
133 - #421 added bookmarks in changelog view
134 - #421 added bookmarks in changelog view
134
135
135 fixes
136 fixes
136 +++++
137 +++++
137
138
138 - fixed dev-version marker for stable when served from source codes
139 - fixed dev-version marker for stable when served from source codes
139 - fixed missing permission checks on show forks page
140 - fixed missing permission checks on show forks page
140 - #418 cast to unicode fixes in notification objects
141 - #418 cast to unicode fixes in notification objects
141 - #426 fixed mention extracting regex
142 - #426 fixed mention extracting regex
142 - fixed remote-pulling for git remotes remopositories
143 - fixed remote-pulling for git remotes remopositories
143 - fixed #434: Error when accessing files or changesets of a git repository
144 - fixed #434: Error when accessing files or changesets of a git repository
144 with submodules
145 with submodules
145 - fixed issue with empty APIKEYS for users after registration ref. #438
146 - fixed issue with empty APIKEYS for users after registration ref. #438
146 - fixed issue with getting README files from git repositories
147 - fixed issue with getting README files from git repositories
147
148
148 1.3.4 (**2012-03-28**)
149 1.3.4 (**2012-03-28**)
149 ----------------------
150 ----------------------
150
151
151 news
152 news
152 ++++
153 ++++
153
154
154 - Whoosh logging is now controlled by the .ini files logging setup
155 - Whoosh logging is now controlled by the .ini files logging setup
155 - added clone-url into edit form on /settings page
156 - added clone-url into edit form on /settings page
156 - added help text into repo add/edit forms
157 - added help text into repo add/edit forms
157 - created rcextensions module with additional mappings (ref #322) and
158 - created rcextensions module with additional mappings (ref #322) and
158 post push/pull/create repo hooks callbacks
159 post push/pull/create repo hooks callbacks
159 - implemented #377 Users view for his own permissions on account page
160 - implemented #377 Users view for his own permissions on account page
160 - #399 added inheritance of permissions for users group on repos groups
161 - #399 added inheritance of permissions for users group on repos groups
161 - #401 repository group is automatically pre-selected when adding repos
162 - #401 repository group is automatically pre-selected when adding repos
162 inside a repository group
163 inside a repository group
163 - added alternative HTTP 403 response when client failed to authenticate. Helps
164 - added alternative HTTP 403 response when client failed to authenticate. Helps
164 solving issues with Mercurial and LDAP
165 solving issues with Mercurial and LDAP
165 - #402 removed group prefix from repository name when listing repositories
166 - #402 removed group prefix from repository name when listing repositories
166 inside a group
167 inside a group
167 - added gravatars into permission view and permissions autocomplete
168 - added gravatars into permission view and permissions autocomplete
168 - #347 when running multiple RhodeCode instances, properly invalidates cache
169 - #347 when running multiple RhodeCode instances, properly invalidates cache
169 for all registered servers
170 for all registered servers
170
171
171 fixes
172 fixes
172 +++++
173 +++++
173
174
174 - fixed #390 cache invalidation problems on repos inside group
175 - fixed #390 cache invalidation problems on repos inside group
175 - fixed #385 clone by ID url was loosing proxy prefix in URL
176 - fixed #385 clone by ID url was loosing proxy prefix in URL
176 - fixed some unicode problems with waitress
177 - fixed some unicode problems with waitress
177 - fixed issue with escaping < and > in changeset commits
178 - fixed issue with escaping < and > in changeset commits
178 - fixed error occurring during recursive group creation in API
179 - fixed error occurring during recursive group creation in API
179 create_repo function
180 create_repo function
180 - fixed #393 py2.5 fixes for routes url generator
181 - fixed #393 py2.5 fixes for routes url generator
181 - fixed #397 Private repository groups shows up before login
182 - fixed #397 Private repository groups shows up before login
182 - fixed #396 fixed problems with revoking users in nested groups
183 - fixed #396 fixed problems with revoking users in nested groups
183 - fixed mysql unicode issues + specified InnoDB as default engine with
184 - fixed mysql unicode issues + specified InnoDB as default engine with
184 utf8 charset
185 utf8 charset
185 - #406 trim long branch/tag names in changelog to not break UI
186 - #406 trim long branch/tag names in changelog to not break UI
186
187
187 1.3.3 (**2012-03-02**)
188 1.3.3 (**2012-03-02**)
188 ----------------------
189 ----------------------
189
190
190 news
191 news
191 ++++
192 ++++
192
193
193
194
194 fixes
195 fixes
195 +++++
196 +++++
196
197
197 - fixed some python2.5 compatibility issues
198 - fixed some python2.5 compatibility issues
198 - fixed issues with removed repos was accidentally added as groups, after
199 - fixed issues with removed repos was accidentally added as groups, after
199 full rescan of paths
200 full rescan of paths
200 - fixes #376 Cannot edit user (using container auth)
201 - fixes #376 Cannot edit user (using container auth)
201 - fixes #378 Invalid image urls on changeset screen with proxy-prefix
202 - fixes #378 Invalid image urls on changeset screen with proxy-prefix
202 configuration
203 configuration
203 - fixed initial sorting of repos inside repo group
204 - fixed initial sorting of repos inside repo group
204 - fixes issue when user tried to resubmit same permission into user/user_groups
205 - fixes issue when user tried to resubmit same permission into user/user_groups
205 - bumped beaker version that fixes #375 leap error bug
206 - bumped beaker version that fixes #375 leap error bug
206 - fixed raw_changeset for git. It was generated with hg patch headers
207 - fixed raw_changeset for git. It was generated with hg patch headers
207 - fixed vcs issue with last_changeset for filenodes
208 - fixed vcs issue with last_changeset for filenodes
208 - fixed missing commit after hook delete
209 - fixed missing commit after hook delete
209 - fixed #372 issues with git operation detection that caused a security issue
210 - fixed #372 issues with git operation detection that caused a security issue
210 for git repos
211 for git repos
211
212
212 1.3.2 (**2012-02-28**)
213 1.3.2 (**2012-02-28**)
213 ----------------------
214 ----------------------
214
215
215 news
216 news
216 ++++
217 ++++
217
218
218
219
219 fixes
220 fixes
220 +++++
221 +++++
221
222
222 - fixed git protocol issues with repos-groups
223 - fixed git protocol issues with repos-groups
223 - fixed git remote repos validator that prevented from cloning remote git repos
224 - fixed git remote repos validator that prevented from cloning remote git repos
224 - fixes #370 ending slashes fixes for repo and groups
225 - fixes #370 ending slashes fixes for repo and groups
225 - fixes #368 improved git-protocol detection to handle other clients
226 - fixes #368 improved git-protocol detection to handle other clients
226 - fixes #366 When Setting Repository Group To Blank Repo Group Wont Be
227 - fixes #366 When Setting Repository Group To Blank Repo Group Wont Be
227 Moved To Root
228 Moved To Root
228 - fixes #371 fixed issues with beaker/sqlalchemy and non-ascii cache keys
229 - fixes #371 fixed issues with beaker/sqlalchemy and non-ascii cache keys
229 - fixed #373 missing cascade drop on user_group_to_perm table
230 - fixed #373 missing cascade drop on user_group_to_perm table
230
231
231 1.3.1 (**2012-02-27**)
232 1.3.1 (**2012-02-27**)
232 ----------------------
233 ----------------------
233
234
234 news
235 news
235 ++++
236 ++++
236
237
237
238
238 fixes
239 fixes
239 +++++
240 +++++
240
241
241 - redirection loop occurs when remember-me wasn't checked during login
242 - redirection loop occurs when remember-me wasn't checked during login
242 - fixes issues with git blob history generation
243 - fixes issues with git blob history generation
243 - don't fetch branch for git in file history dropdown. Causes unneeded slowness
244 - don't fetch branch for git in file history dropdown. Causes unneeded slowness
244
245
245 1.3.0 (**2012-02-26**)
246 1.3.0 (**2012-02-26**)
246 ----------------------
247 ----------------------
247
248
248 news
249 news
249 ++++
250 ++++
250
251
251 - code review, inspired by github code-comments
252 - code review, inspired by github code-comments
252 - #215 rst and markdown README files support
253 - #215 rst and markdown README files support
253 - #252 Container-based and proxy pass-through authentication support
254 - #252 Container-based and proxy pass-through authentication support
254 - #44 branch browser. Filtering of changelog by branches
255 - #44 branch browser. Filtering of changelog by branches
255 - mercurial bookmarks support
256 - mercurial bookmarks support
256 - new hover top menu, optimized to add maximum size for important views
257 - new hover top menu, optimized to add maximum size for important views
257 - configurable clone url template with possibility to specify protocol like
258 - configurable clone url template with possibility to specify protocol like
258 ssh:// or http:// and also manually alter other parts of clone_url.
259 ssh:// or http:// and also manually alter other parts of clone_url.
259 - enabled largefiles extension by default
260 - enabled largefiles extension by default
260 - optimized summary file pages and saved a lot of unused space in them
261 - optimized summary file pages and saved a lot of unused space in them
261 - #239 option to manually mark repository as fork
262 - #239 option to manually mark repository as fork
262 - #320 mapping of commit authors to RhodeCode users
263 - #320 mapping of commit authors to RhodeCode users
263 - #304 hashes are displayed using monospace font
264 - #304 hashes are displayed using monospace font
264 - diff configuration, toggle white lines and context lines
265 - diff configuration, toggle white lines and context lines
265 - #307 configurable diffs, whitespace toggle, increasing context lines
266 - #307 configurable diffs, whitespace toggle, increasing context lines
266 - sorting on branches, tags and bookmarks using YUI datatable
267 - sorting on branches, tags and bookmarks using YUI datatable
267 - improved file filter on files page
268 - improved file filter on files page
268 - implements #330 api method for listing nodes ar particular revision
269 - implements #330 api method for listing nodes ar particular revision
269 - #73 added linking issues in commit messages to chosen issue tracker url
270 - #73 added linking issues in commit messages to chosen issue tracker url
270 based on user defined regular expression
271 based on user defined regular expression
271 - added linking of changesets in commit messages
272 - added linking of changesets in commit messages
272 - new compact changelog with expandable commit messages
273 - new compact changelog with expandable commit messages
273 - firstname and lastname are optional in user creation
274 - firstname and lastname are optional in user creation
274 - #348 added post-create repository hook
275 - #348 added post-create repository hook
275 - #212 global encoding settings is now configurable from .ini files
276 - #212 global encoding settings is now configurable from .ini files
276 - #227 added repository groups permissions
277 - #227 added repository groups permissions
277 - markdown gets codehilite extensions
278 - markdown gets codehilite extensions
278 - new API methods, delete_repositories, grante/revoke permissions for groups
279 - new API methods, delete_repositories, grante/revoke permissions for groups
279 and repos
280 and repos
280
281
281
282
282 fixes
283 fixes
283 +++++
284 +++++
284
285
285 - rewrote dbsession management for atomic operations, and better error handling
286 - rewrote dbsession management for atomic operations, and better error handling
286 - fixed sorting of repo tables
287 - fixed sorting of repo tables
287 - #326 escape of special html entities in diffs
288 - #326 escape of special html entities in diffs
288 - normalized user_name => username in api attributes
289 - normalized user_name => username in api attributes
289 - fixes #298 ldap created users with mixed case emails created conflicts
290 - fixes #298 ldap created users with mixed case emails created conflicts
290 on saving a form
291 on saving a form
291 - fixes issue when owner of a repo couldn't revoke permissions for users
292 - fixes issue when owner of a repo couldn't revoke permissions for users
292 and groups
293 and groups
293 - fixes #271 rare JSON serialization problem with statistics
294 - fixes #271 rare JSON serialization problem with statistics
294 - fixes #337 missing validation check for conflicting names of a group with a
295 - fixes #337 missing validation check for conflicting names of a group with a
295 repositories group
296 repositories group
296 - #340 fixed session problem for mysql and celery tasks
297 - #340 fixed session problem for mysql and celery tasks
297 - fixed #331 RhodeCode mangles repository names if the a repository group
298 - fixed #331 RhodeCode mangles repository names if the a repository group
298 contains the "full path" to the repositories
299 contains the "full path" to the repositories
299 - #355 RhodeCode doesn't store encrypted LDAP passwords
300 - #355 RhodeCode doesn't store encrypted LDAP passwords
300
301
301 1.2.5 (**2012-01-28**)
302 1.2.5 (**2012-01-28**)
302 ----------------------
303 ----------------------
303
304
304 news
305 news
305 ++++
306 ++++
306
307
307 fixes
308 fixes
308 +++++
309 +++++
309
310
310 - #340 Celery complains about MySQL server gone away, added session cleanup
311 - #340 Celery complains about MySQL server gone away, added session cleanup
311 for celery tasks
312 for celery tasks
312 - #341 "scanning for repositories in None" log message during Rescan was missing
313 - #341 "scanning for repositories in None" log message during Rescan was missing
313 a parameter
314 a parameter
314 - fixed creating archives with subrepos. Some hooks were triggered during that
315 - fixed creating archives with subrepos. Some hooks were triggered during that
315 operation leading to crash.
316 operation leading to crash.
316 - fixed missing email in account page.
317 - fixed missing email in account page.
317 - Reverted Mercurial to 2.0.1 for windows due to bug in Mercurial that makes
318 - Reverted Mercurial to 2.0.1 for windows due to bug in Mercurial that makes
318 forking on windows impossible
319 forking on windows impossible
319
320
320 1.2.4 (**2012-01-19**)
321 1.2.4 (**2012-01-19**)
321 ----------------------
322 ----------------------
322
323
323 news
324 news
324 ++++
325 ++++
325
326
326 - RhodeCode is bundled with mercurial series 2.0.X by default, with
327 - RhodeCode is bundled with mercurial series 2.0.X by default, with
327 full support to largefiles extension. Enabled by default in new installations
328 full support to largefiles extension. Enabled by default in new installations
328 - #329 Ability to Add/Remove Groups to/from a Repository via AP
329 - #329 Ability to Add/Remove Groups to/from a Repository via AP
329 - added requires.txt file with requirements
330 - added requires.txt file with requirements
330
331
331 fixes
332 fixes
332 +++++
333 +++++
333
334
334 - fixes db session issues with celery when emailing admins
335 - fixes db session issues with celery when emailing admins
335 - #331 RhodeCode mangles repository names if the a repository group
336 - #331 RhodeCode mangles repository names if the a repository group
336 contains the "full path" to the repositories
337 contains the "full path" to the repositories
337 - #298 Conflicting e-mail addresses for LDAP and RhodeCode users
338 - #298 Conflicting e-mail addresses for LDAP and RhodeCode users
338 - DB session cleanup after hg protocol operations, fixes issues with
339 - DB session cleanup after hg protocol operations, fixes issues with
339 `mysql has gone away` errors
340 `mysql has gone away` errors
340 - #333 doc fixes for get_repo api function
341 - #333 doc fixes for get_repo api function
341 - #271 rare JSON serialization problem with statistics enabled
342 - #271 rare JSON serialization problem with statistics enabled
342 - #337 Fixes issues with validation of repository name conflicting with
343 - #337 Fixes issues with validation of repository name conflicting with
343 a group name. A proper message is now displayed.
344 a group name. A proper message is now displayed.
344 - #292 made ldap_dn in user edit readonly, to get rid of confusion that field
345 - #292 made ldap_dn in user edit readonly, to get rid of confusion that field
345 doesn't work
346 doesn't work
346 - #316 fixes issues with web description in hgrc files
347 - #316 fixes issues with web description in hgrc files
347
348
348 1.2.3 (**2011-11-02**)
349 1.2.3 (**2011-11-02**)
349 ----------------------
350 ----------------------
350
351
351 news
352 news
352 ++++
353 ++++
353
354
354 - added option to manage repos group for non admin users
355 - added option to manage repos group for non admin users
355 - added following API methods for get_users, create_user, get_users_groups,
356 - added following API methods for get_users, create_user, get_users_groups,
356 get_users_group, create_users_group, add_user_to_users_groups, get_repos,
357 get_users_group, create_users_group, add_user_to_users_groups, get_repos,
357 get_repo, create_repo, add_user_to_repo
358 get_repo, create_repo, add_user_to_repo
358 - implements #237 added password confirmation for my account
359 - implements #237 added password confirmation for my account
359 and admin edit user.
360 and admin edit user.
360 - implements #291 email notification for global events are now sent to all
361 - implements #291 email notification for global events are now sent to all
361 administrator users, and global config email.
362 administrator users, and global config email.
362
363
363 fixes
364 fixes
364 +++++
365 +++++
365
366
366 - added option for passing auth method for smtp mailer
367 - added option for passing auth method for smtp mailer
367 - #276 issue with adding a single user with id>10 to usergroups
368 - #276 issue with adding a single user with id>10 to usergroups
368 - #277 fixes windows LDAP settings in which missing values breaks the ldap auth
369 - #277 fixes windows LDAP settings in which missing values breaks the ldap auth
369 - #288 fixes managing of repos in a group for non admin user
370 - #288 fixes managing of repos in a group for non admin user
370
371
371 1.2.2 (**2011-10-17**)
372 1.2.2 (**2011-10-17**)
372 ----------------------
373 ----------------------
373
374
374 news
375 news
375 ++++
376 ++++
376
377
377 - #226 repo groups are available by path instead of numerical id
378 - #226 repo groups are available by path instead of numerical id
378
379
379 fixes
380 fixes
380 +++++
381 +++++
381
382
382 - #259 Groups with the same name but with different parent group
383 - #259 Groups with the same name but with different parent group
383 - #260 Put repo in group, then move group to another group -> repo becomes unavailable
384 - #260 Put repo in group, then move group to another group -> repo becomes unavailable
384 - #258 RhodeCode 1.2 assumes egg folder is writable (lockfiles problems)
385 - #258 RhodeCode 1.2 assumes egg folder is writable (lockfiles problems)
385 - #265 ldap save fails sometimes on converting attributes to booleans,
386 - #265 ldap save fails sometimes on converting attributes to booleans,
386 added getter and setter into model that will prevent from this on db model level
387 added getter and setter into model that will prevent from this on db model level
387 - fixed problems with timestamps issues #251 and #213
388 - fixed problems with timestamps issues #251 and #213
388 - fixes #266 RhodeCode allows to create repo with the same name and in
389 - fixes #266 RhodeCode allows to create repo with the same name and in
389 the same parent as group
390 the same parent as group
390 - fixes #245 Rescan of the repositories on Windows
391 - fixes #245 Rescan of the repositories on Windows
391 - fixes #248 cannot edit repos inside a group on windows
392 - fixes #248 cannot edit repos inside a group on windows
392 - fixes #219 forking problems on windows
393 - fixes #219 forking problems on windows
393
394
394 1.2.1 (**2011-10-08**)
395 1.2.1 (**2011-10-08**)
395 ----------------------
396 ----------------------
396
397
397 news
398 news
398 ++++
399 ++++
399
400
400
401
401 fixes
402 fixes
402 +++++
403 +++++
403
404
404 - fixed problems with basic auth and push problems
405 - fixed problems with basic auth and push problems
405 - gui fixes
406 - gui fixes
406 - fixed logger
407 - fixed logger
407
408
408 1.2.0 (**2011-10-07**)
409 1.2.0 (**2011-10-07**)
409 ----------------------
410 ----------------------
410
411
411 news
412 news
412 ++++
413 ++++
413
414
414 - implemented #47 repository groups
415 - implemented #47 repository groups
415 - implemented #89 Can setup google analytics code from settings menu
416 - implemented #89 Can setup google analytics code from settings menu
416 - implemented #91 added nicer looking archive urls with more download options
417 - implemented #91 added nicer looking archive urls with more download options
417 like tags, branches
418 like tags, branches
418 - implemented #44 into file browsing, and added follow branch option
419 - implemented #44 into file browsing, and added follow branch option
419 - implemented #84 downloads can be enabled/disabled for each repository
420 - implemented #84 downloads can be enabled/disabled for each repository
420 - anonymous repository can be cloned without having to pass default:default
421 - anonymous repository can be cloned without having to pass default:default
421 into clone url
422 into clone url
422 - fixed #90 whoosh indexer can index chooses repositories passed in command
423 - fixed #90 whoosh indexer can index chooses repositories passed in command
423 line
424 line
424 - extended journal with day aggregates and paging
425 - extended journal with day aggregates and paging
425 - implemented #107 source code lines highlight ranges
426 - implemented #107 source code lines highlight ranges
426 - implemented #93 customizable changelog on combined revision ranges -
427 - implemented #93 customizable changelog on combined revision ranges -
427 equivalent of githubs compare view
428 equivalent of githubs compare view
428 - implemented #108 extended and more powerful LDAP configuration
429 - implemented #108 extended and more powerful LDAP configuration
429 - implemented #56 users groups
430 - implemented #56 users groups
430 - major code rewrites optimized codes for speed and memory usage
431 - major code rewrites optimized codes for speed and memory usage
431 - raw and diff downloads are now in git format
432 - raw and diff downloads are now in git format
432 - setup command checks for write access to given path
433 - setup command checks for write access to given path
433 - fixed many issues with international characters and unicode. It uses utf8
434 - fixed many issues with international characters and unicode. It uses utf8
434 decode with replace to provide less errors even with non utf8 encoded strings
435 decode with replace to provide less errors even with non utf8 encoded strings
435 - #125 added API KEY access to feeds
436 - #125 added API KEY access to feeds
436 - #109 Repository can be created from external Mercurial link (aka. remote
437 - #109 Repository can be created from external Mercurial link (aka. remote
437 repository, and manually updated (via pull) from admin panel
438 repository, and manually updated (via pull) from admin panel
438 - beta git support - push/pull server + basic view for git repos
439 - beta git support - push/pull server + basic view for git repos
439 - added followers page and forks page
440 - added followers page and forks page
440 - server side file creation (with binary file upload interface)
441 - server side file creation (with binary file upload interface)
441 and edition with commits powered by codemirror
442 and edition with commits powered by codemirror
442 - #111 file browser file finder, quick lookup files on whole file tree
443 - #111 file browser file finder, quick lookup files on whole file tree
443 - added quick login sliding menu into main page
444 - added quick login sliding menu into main page
444 - changelog uses lazy loading of affected files details, in some scenarios
445 - changelog uses lazy loading of affected files details, in some scenarios
445 this can improve speed of changelog page dramatically especially for
446 this can improve speed of changelog page dramatically especially for
446 larger repositories.
447 larger repositories.
447 - implements #214 added support for downloading subrepos in download menu.
448 - implements #214 added support for downloading subrepos in download menu.
448 - Added basic API for direct operations on rhodecode via JSON
449 - Added basic API for direct operations on rhodecode via JSON
449 - Implemented advanced hook management
450 - Implemented advanced hook management
450
451
451 fixes
452 fixes
452 +++++
453 +++++
453
454
454 - fixed file browser bug, when switching into given form revision the url was
455 - fixed file browser bug, when switching into given form revision the url was
455 not changing
456 not changing
456 - fixed propagation to error controller on simplehg and simplegit middlewares
457 - fixed propagation to error controller on simplehg and simplegit middlewares
457 - fixed error when trying to make a download on empty repository
458 - fixed error when trying to make a download on empty repository
458 - fixed problem with '[' chars in commit messages in journal
459 - fixed problem with '[' chars in commit messages in journal
459 - fixed #99 Unicode errors, on file node paths with non utf-8 characters
460 - fixed #99 Unicode errors, on file node paths with non utf-8 characters
460 - journal fork fixes
461 - journal fork fixes
461 - removed issue with space inside renamed repository after deletion
462 - removed issue with space inside renamed repository after deletion
462 - fixed strange issue on formencode imports
463 - fixed strange issue on formencode imports
463 - fixed #126 Deleting repository on Windows, rename used incompatible chars.
464 - fixed #126 Deleting repository on Windows, rename used incompatible chars.
464 - #150 fixes for errors on repositories mapped in db but corrupted in
465 - #150 fixes for errors on repositories mapped in db but corrupted in
465 filesystem
466 filesystem
466 - fixed problem with ascendant characters in realm #181
467 - fixed problem with ascendant characters in realm #181
467 - fixed problem with sqlite file based database connection pool
468 - fixed problem with sqlite file based database connection pool
468 - whoosh indexer and code stats share the same dynamic extensions map
469 - whoosh indexer and code stats share the same dynamic extensions map
469 - fixes #188 - relationship delete of repo_to_perm entry on user removal
470 - fixes #188 - relationship delete of repo_to_perm entry on user removal
470 - fixes issue #189 Trending source files shows "show more" when no more exist
471 - fixes issue #189 Trending source files shows "show more" when no more exist
471 - fixes issue #197 Relative paths for pidlocks
472 - fixes issue #197 Relative paths for pidlocks
472 - fixes issue #198 password will require only 3 chars now for login form
473 - fixes issue #198 password will require only 3 chars now for login form
473 - fixes issue #199 wrong redirection for non admin users after creating a repository
474 - fixes issue #199 wrong redirection for non admin users after creating a repository
474 - fixes issues #202, bad db constraint made impossible to attach same group
475 - fixes issues #202, bad db constraint made impossible to attach same group
475 more than one time. Affects only mysql/postgres
476 more than one time. Affects only mysql/postgres
476 - fixes #218 os.kill patch for windows was missing sig param
477 - fixes #218 os.kill patch for windows was missing sig param
477 - improved rendering of dag (they are not trimmed anymore when number of
478 - improved rendering of dag (they are not trimmed anymore when number of
478 heads exceeds 5)
479 heads exceeds 5)
479
480
480 1.1.8 (**2011-04-12**)
481 1.1.8 (**2011-04-12**)
481 ----------------------
482 ----------------------
482
483
483 news
484 news
484 ++++
485 ++++
485
486
486 - improved windows support
487 - improved windows support
487
488
488 fixes
489 fixes
489 +++++
490 +++++
490
491
491 - fixed #140 freeze of python dateutil library, since new version is python2.x
492 - fixed #140 freeze of python dateutil library, since new version is python2.x
492 incompatible
493 incompatible
493 - setup-app will check for write permission in given path
494 - setup-app will check for write permission in given path
494 - cleaned up license info issue #149
495 - cleaned up license info issue #149
495 - fixes for issues #137,#116 and problems with unicode and accented characters.
496 - fixes for issues #137,#116 and problems with unicode and accented characters.
496 - fixes crashes on gravatar, when passed in email as unicode
497 - fixes crashes on gravatar, when passed in email as unicode
497 - fixed tooltip flickering problems
498 - fixed tooltip flickering problems
498 - fixed came_from redirection on windows
499 - fixed came_from redirection on windows
499 - fixed logging modules, and sql formatters
500 - fixed logging modules, and sql formatters
500 - windows fixes for os.kill issue #133
501 - windows fixes for os.kill issue #133
501 - fixes path splitting for windows issues #148
502 - fixes path splitting for windows issues #148
502 - fixed issue #143 wrong import on migration to 1.1.X
503 - fixed issue #143 wrong import on migration to 1.1.X
503 - fixed problems with displaying binary files, thanks to Thomas Waldmann
504 - fixed problems with displaying binary files, thanks to Thomas Waldmann
504 - removed name from archive files since it's breaking ui for long repo names
505 - removed name from archive files since it's breaking ui for long repo names
505 - fixed issue with archive headers sent to browser, thanks to Thomas Waldmann
506 - fixed issue with archive headers sent to browser, thanks to Thomas Waldmann
506 - fixed compatibility for 1024px displays, and larger dpi settings, thanks to
507 - fixed compatibility for 1024px displays, and larger dpi settings, thanks to
507 Thomas Waldmann
508 Thomas Waldmann
508 - fixed issue #166 summary pager was skipping 10 revisions on second page
509 - fixed issue #166 summary pager was skipping 10 revisions on second page
509
510
510
511
511 1.1.7 (**2011-03-23**)
512 1.1.7 (**2011-03-23**)
512 ----------------------
513 ----------------------
513
514
514 news
515 news
515 ++++
516 ++++
516
517
517 fixes
518 fixes
518 +++++
519 +++++
519
520
520 - fixed (again) #136 installation support for FreeBSD
521 - fixed (again) #136 installation support for FreeBSD
521
522
522
523
523 1.1.6 (**2011-03-21**)
524 1.1.6 (**2011-03-21**)
524 ----------------------
525 ----------------------
525
526
526 news
527 news
527 ++++
528 ++++
528
529
529 fixes
530 fixes
530 +++++
531 +++++
531
532
532 - fixed #136 installation support for FreeBSD
533 - fixed #136 installation support for FreeBSD
533 - RhodeCode will check for python version during installation
534 - RhodeCode will check for python version during installation
534
535
535 1.1.5 (**2011-03-17**)
536 1.1.5 (**2011-03-17**)
536 ----------------------
537 ----------------------
537
538
538 news
539 news
539 ++++
540 ++++
540
541
541 - basic windows support, by exchanging pybcrypt into sha256 for windows only
542 - basic windows support, by exchanging pybcrypt into sha256 for windows only
542 highly inspired by idea of mantis406
543 highly inspired by idea of mantis406
543
544
544 fixes
545 fixes
545 +++++
546 +++++
546
547
547 - fixed sorting by author in main page
548 - fixed sorting by author in main page
548 - fixed crashes with diffs on binary files
549 - fixed crashes with diffs on binary files
549 - fixed #131 problem with boolean values for LDAP
550 - fixed #131 problem with boolean values for LDAP
550 - fixed #122 mysql problems thanks to striker69
551 - fixed #122 mysql problems thanks to striker69
551 - fixed problem with errors on calling raw/raw_files/annotate functions
552 - fixed problem with errors on calling raw/raw_files/annotate functions
552 with unknown revisions
553 with unknown revisions
553 - fixed returned rawfiles attachment names with international character
554 - fixed returned rawfiles attachment names with international character
554 - cleaned out docs, big thanks to Jason Harris
555 - cleaned out docs, big thanks to Jason Harris
555
556
556 1.1.4 (**2011-02-19**)
557 1.1.4 (**2011-02-19**)
557 ----------------------
558 ----------------------
558
559
559 news
560 news
560 ++++
561 ++++
561
562
562 fixes
563 fixes
563 +++++
564 +++++
564
565
565 - fixed formencode import problem on settings page, that caused server crash
566 - fixed formencode import problem on settings page, that caused server crash
566 when that page was accessed as first after server start
567 when that page was accessed as first after server start
567 - journal fixes
568 - journal fixes
568 - fixed option to access repository just by entering http://server/<repo_name>
569 - fixed option to access repository just by entering http://server/<repo_name>
569
570
570 1.1.3 (**2011-02-16**)
571 1.1.3 (**2011-02-16**)
571 ----------------------
572 ----------------------
572
573
573 news
574 news
574 ++++
575 ++++
575
576
576 - implemented #102 allowing the '.' character in username
577 - implemented #102 allowing the '.' character in username
577 - added option to access repository just by entering http://server/<repo_name>
578 - added option to access repository just by entering http://server/<repo_name>
578 - celery task ignores result for better performance
579 - celery task ignores result for better performance
579
580
580 fixes
581 fixes
581 +++++
582 +++++
582
583
583 - fixed ehlo command and non auth mail servers on smtp_lib. Thanks to
584 - fixed ehlo command and non auth mail servers on smtp_lib. Thanks to
584 apollo13 and Johan Walles
585 apollo13 and Johan Walles
585 - small fixes in journal
586 - small fixes in journal
586 - fixed problems with getting setting for celery from .ini files
587 - fixed problems with getting setting for celery from .ini files
587 - registration, password reset and login boxes share the same title as main
588 - registration, password reset and login boxes share the same title as main
588 application now
589 application now
589 - fixed #113: to high permissions to fork repository
590 - fixed #113: to high permissions to fork repository
590 - fixed problem with '[' chars in commit messages in journal
591 - fixed problem with '[' chars in commit messages in journal
591 - removed issue with space inside renamed repository after deletion
592 - removed issue with space inside renamed repository after deletion
592 - db transaction fixes when filesystem repository creation failed
593 - db transaction fixes when filesystem repository creation failed
593 - fixed #106 relation issues on databases different than sqlite
594 - fixed #106 relation issues on databases different than sqlite
594 - fixed static files paths links to use of url() method
595 - fixed static files paths links to use of url() method
595
596
596 1.1.2 (**2011-01-12**)
597 1.1.2 (**2011-01-12**)
597 ----------------------
598 ----------------------
598
599
599 news
600 news
600 ++++
601 ++++
601
602
602
603
603 fixes
604 fixes
604 +++++
605 +++++
605
606
606 - fixes #98 protection against float division of percentage stats
607 - fixes #98 protection against float division of percentage stats
607 - fixed graph bug
608 - fixed graph bug
608 - forced webhelpers version since it was making troubles during installation
609 - forced webhelpers version since it was making troubles during installation
609
610
610 1.1.1 (**2011-01-06**)
611 1.1.1 (**2011-01-06**)
611 ----------------------
612 ----------------------
612
613
613 news
614 news
614 ++++
615 ++++
615
616
616 - added force https option into ini files for easier https usage (no need to
617 - added force https option into ini files for easier https usage (no need to
617 set server headers with this options)
618 set server headers with this options)
618 - small css updates
619 - small css updates
619
620
620 fixes
621 fixes
621 +++++
622 +++++
622
623
623 - fixed #96 redirect loop on files view on repositories without changesets
624 - fixed #96 redirect loop on files view on repositories without changesets
624 - fixed #97 unicode string passed into server header in special cases (mod_wsgi)
625 - fixed #97 unicode string passed into server header in special cases (mod_wsgi)
625 and server crashed with errors
626 and server crashed with errors
626 - fixed large tooltips problems on main page
627 - fixed large tooltips problems on main page
627 - fixed #92 whoosh indexer is more error proof
628 - fixed #92 whoosh indexer is more error proof
628
629
629 1.1.0 (**2010-12-18**)
630 1.1.0 (**2010-12-18**)
630 ----------------------
631 ----------------------
631
632
632 news
633 news
633 ++++
634 ++++
634
635
635 - rewrite of internals for vcs >=0.1.10
636 - rewrite of internals for vcs >=0.1.10
636 - uses mercurial 1.7 with dotencode disabled for maintaining compatibility
637 - uses mercurial 1.7 with dotencode disabled for maintaining compatibility
637 with older clients
638 with older clients
638 - anonymous access, authentication via ldap
639 - anonymous access, authentication via ldap
639 - performance upgrade for cached repos list - each repository has its own
640 - performance upgrade for cached repos list - each repository has its own
640 cache that's invalidated when needed.
641 cache that's invalidated when needed.
641 - performance upgrades on repositories with large amount of commits (20K+)
642 - performance upgrades on repositories with large amount of commits (20K+)
642 - main page quick filter for filtering repositories
643 - main page quick filter for filtering repositories
643 - user dashboards with ability to follow chosen repositories actions
644 - user dashboards with ability to follow chosen repositories actions
644 - sends email to admin on new user registration
645 - sends email to admin on new user registration
645 - added cache/statistics reset options into repository settings
646 - added cache/statistics reset options into repository settings
646 - more detailed action logger (based on hooks) with pushed changesets lists
647 - more detailed action logger (based on hooks) with pushed changesets lists
647 and options to disable those hooks from admin panel
648 and options to disable those hooks from admin panel
648 - introduced new enhanced changelog for merges that shows more accurate results
649 - introduced new enhanced changelog for merges that shows more accurate results
649 - new improved and faster code stats (based on pygments lexers mapping tables,
650 - new improved and faster code stats (based on pygments lexers mapping tables,
650 showing up to 10 trending sources for each repository. Additionally stats
651 showing up to 10 trending sources for each repository. Additionally stats
651 can be disabled in repository settings.
652 can be disabled in repository settings.
652 - gui optimizations, fixed application width to 1024px
653 - gui optimizations, fixed application width to 1024px
653 - added cut off (for large files/changesets) limit into config files
654 - added cut off (for large files/changesets) limit into config files
654 - whoosh, celeryd, upgrade moved to paster command
655 - whoosh, celeryd, upgrade moved to paster command
655 - other than sqlite database backends can be used
656 - other than sqlite database backends can be used
656
657
657 fixes
658 fixes
658 +++++
659 +++++
659
660
660 - fixes #61 forked repo was showing only after cache expired
661 - fixes #61 forked repo was showing only after cache expired
661 - fixes #76 no confirmation on user deletes
662 - fixes #76 no confirmation on user deletes
662 - fixes #66 Name field misspelled
663 - fixes #66 Name field misspelled
663 - fixes #72 block user removal when he owns repositories
664 - fixes #72 block user removal when he owns repositories
664 - fixes #69 added password confirmation fields
665 - fixes #69 added password confirmation fields
665 - fixes #87 RhodeCode crashes occasionally on updating repository owner
666 - fixes #87 RhodeCode crashes occasionally on updating repository owner
666 - fixes #82 broken annotations on files with more than 1 blank line at the end
667 - fixes #82 broken annotations on files with more than 1 blank line at the end
667 - a lot of fixes and tweaks for file browser
668 - a lot of fixes and tweaks for file browser
668 - fixed detached session issues
669 - fixed detached session issues
669 - fixed when user had no repos he would see all repos listed in my account
670 - fixed when user had no repos he would see all repos listed in my account
670 - fixed ui() instance bug when global hgrc settings was loaded for server
671 - fixed ui() instance bug when global hgrc settings was loaded for server
671 instance and all hgrc options were merged with our db ui() object
672 instance and all hgrc options were merged with our db ui() object
672 - numerous small bugfixes
673 - numerous small bugfixes
673
674
674 (special thanks for TkSoh for detailed feedback)
675 (special thanks for TkSoh for detailed feedback)
675
676
676
677
677 1.0.2 (**2010-11-12**)
678 1.0.2 (**2010-11-12**)
678 ----------------------
679 ----------------------
679
680
680 news
681 news
681 ++++
682 ++++
682
683
683 - tested under python2.7
684 - tested under python2.7
684 - bumped sqlalchemy and celery versions
685 - bumped sqlalchemy and celery versions
685
686
686 fixes
687 fixes
687 +++++
688 +++++
688
689
689 - fixed #59 missing graph.js
690 - fixed #59 missing graph.js
690 - fixed repo_size crash when repository had broken symlinks
691 - fixed repo_size crash when repository had broken symlinks
691 - fixed python2.5 crashes.
692 - fixed python2.5 crashes.
692
693
693
694
694 1.0.1 (**2010-11-10**)
695 1.0.1 (**2010-11-10**)
695 ----------------------
696 ----------------------
696
697
697 news
698 news
698 ++++
699 ++++
699
700
700 - small css updated
701 - small css updated
701
702
702 fixes
703 fixes
703 +++++
704 +++++
704
705
705 - fixed #53 python2.5 incompatible enumerate calls
706 - fixed #53 python2.5 incompatible enumerate calls
706 - fixed #52 disable mercurial extension for web
707 - fixed #52 disable mercurial extension for web
707 - fixed #51 deleting repositories don't delete it's dependent objects
708 - fixed #51 deleting repositories don't delete it's dependent objects
708
709
709
710
710 1.0.0 (**2010-11-02**)
711 1.0.0 (**2010-11-02**)
711 ----------------------
712 ----------------------
712
713
713 - security bugfix simplehg wasn't checking for permissions on commands
714 - security bugfix simplehg wasn't checking for permissions on commands
714 other than pull or push.
715 other than pull or push.
715 - fixed doubled messages after push or pull in admin journal
716 - fixed doubled messages after push or pull in admin journal
716 - templating and css corrections, fixed repo switcher on chrome, updated titles
717 - templating and css corrections, fixed repo switcher on chrome, updated titles
717 - admin menu accessible from options menu on repository view
718 - admin menu accessible from options menu on repository view
718 - permissions cached queries
719 - permissions cached queries
719
720
720 1.0.0rc4 (**2010-10-12**)
721 1.0.0rc4 (**2010-10-12**)
721 --------------------------
722 --------------------------
722
723
723 - fixed python2.5 missing simplejson imports (thanks to Jens BΓ€ckman)
724 - fixed python2.5 missing simplejson imports (thanks to Jens BΓ€ckman)
724 - removed cache_manager settings from sqlalchemy meta
725 - removed cache_manager settings from sqlalchemy meta
725 - added sqlalchemy cache settings to ini files
726 - added sqlalchemy cache settings to ini files
726 - validated password length and added second try of failure on paster setup-app
727 - validated password length and added second try of failure on paster setup-app
727 - fixed setup database destroy prompt even when there was no db
728 - fixed setup database destroy prompt even when there was no db
728
729
729
730
730 1.0.0rc3 (**2010-10-11**)
731 1.0.0rc3 (**2010-10-11**)
731 -------------------------
732 -------------------------
732
733
733 - fixed i18n during installation.
734 - fixed i18n during installation.
734
735
735 1.0.0rc2 (**2010-10-11**)
736 1.0.0rc2 (**2010-10-11**)
736 -------------------------
737 -------------------------
737
738
738 - Disabled dirsize in file browser, it's causing nasty bug when dir renames
739 - Disabled dirsize in file browser, it's causing nasty bug when dir renames
739 occure. After vcs is fixed it'll be put back again.
740 occure. After vcs is fixed it'll be put back again.
740 - templating/css rewrites, optimized css. No newline at end of file
741 - templating/css rewrites, optimized css.
@@ -1,636 +1,636 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.diffs
3 rhodecode.lib.diffs
4 ~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~
5
5
6 Set of diffing helpers, previously part of vcs
6 Set of diffing helpers, previously part of vcs
7
7
8
8
9 :created_on: Dec 4, 2011
9 :created_on: Dec 4, 2011
10 :author: marcink
10 :author: marcink
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
12 :original copyright: 2007-2008 by Armin Ronacher
12 :original copyright: 2007-2008 by Armin Ronacher
13 :license: GPLv3, see COPYING for more details.
13 :license: GPLv3, see COPYING for more details.
14 """
14 """
15 # This program is free software: you can redistribute it and/or modify
15 # This program is free software: you can redistribute it and/or modify
16 # it under the terms of the GNU General Public License as published by
16 # it under the terms of the GNU General Public License as published by
17 # the Free Software Foundation, either version 3 of the License, or
17 # the Free Software Foundation, either version 3 of the License, or
18 # (at your option) any later version.
18 # (at your option) any later version.
19 #
19 #
20 # This program is distributed in the hope that it will be useful,
20 # This program is distributed in the hope that it will be useful,
21 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # but WITHOUT ANY WARRANTY; without even the implied warranty of
22 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 # GNU General Public License for more details.
23 # GNU General Public License for more details.
24 #
24 #
25 # You should have received a copy of the GNU General Public License
25 # You should have received a copy of the GNU General Public License
26 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 # along with this program. If not, see <http://www.gnu.org/licenses/>.
27
27
28 import re
28 import re
29 import difflib
29 import difflib
30 import markupsafe
30 import markupsafe
31
31
32 from itertools import tee, imap
32 from itertools import tee, imap
33
33
34 from mercurial import patch
34 from mercurial import patch
35 from mercurial.mdiff import diffopts
35 from mercurial.mdiff import diffopts
36 from mercurial.bundlerepo import bundlerepository
36 from mercurial.bundlerepo import bundlerepository
37
37
38 from pylons.i18n.translation import _
38 from pylons.i18n.translation import _
39
39
40 from rhodecode.lib.compat import BytesIO
40 from rhodecode.lib.compat import BytesIO
41 from rhodecode.lib.vcs.utils.hgcompat import localrepo
41 from rhodecode.lib.vcs.utils.hgcompat import localrepo
42 from rhodecode.lib.vcs.exceptions import VCSError
42 from rhodecode.lib.vcs.exceptions import VCSError
43 from rhodecode.lib.vcs.nodes import FileNode, SubModuleNode
43 from rhodecode.lib.vcs.nodes import FileNode, SubModuleNode
44 from rhodecode.lib.vcs.backends.base import EmptyChangeset
44 from rhodecode.lib.vcs.backends.base import EmptyChangeset
45 from rhodecode.lib.helpers import escape
45 from rhodecode.lib.helpers import escape
46 from rhodecode.lib.utils import make_ui
46 from rhodecode.lib.utils import make_ui
47
47
48
48
49 def wrap_to_table(str_):
49 def wrap_to_table(str_):
50 return '''<table class="code-difftable">
50 return '''<table class="code-difftable">
51 <tr class="line no-comment">
51 <tr class="line no-comment">
52 <td class="lineno new"></td>
52 <td class="lineno new"></td>
53 <td class="code no-comment"><pre>%s</pre></td>
53 <td class="code no-comment"><pre>%s</pre></td>
54 </tr>
54 </tr>
55 </table>''' % str_
55 </table>''' % str_
56
56
57
57
58 def wrapped_diff(filenode_old, filenode_new, cut_off_limit=None,
58 def wrapped_diff(filenode_old, filenode_new, cut_off_limit=None,
59 ignore_whitespace=True, line_context=3,
59 ignore_whitespace=True, line_context=3,
60 enable_comments=False):
60 enable_comments=False):
61 """
61 """
62 returns a wrapped diff into a table, checks for cut_off_limit and presents
62 returns a wrapped diff into a table, checks for cut_off_limit and presents
63 proper message
63 proper message
64 """
64 """
65
65
66 if filenode_old is None:
66 if filenode_old is None:
67 filenode_old = FileNode(filenode_new.path, '', EmptyChangeset())
67 filenode_old = FileNode(filenode_new.path, '', EmptyChangeset())
68
68
69 if filenode_old.is_binary or filenode_new.is_binary:
69 if filenode_old.is_binary or filenode_new.is_binary:
70 diff = wrap_to_table(_('binary file'))
70 diff = wrap_to_table(_('binary file'))
71 stats = (0, 0)
71 stats = (0, 0)
72 size = 0
72 size = 0
73
73
74 elif cut_off_limit != -1 and (cut_off_limit is None or
74 elif cut_off_limit != -1 and (cut_off_limit is None or
75 (filenode_old.size < cut_off_limit and filenode_new.size < cut_off_limit)):
75 (filenode_old.size < cut_off_limit and filenode_new.size < cut_off_limit)):
76
76
77 f_gitdiff = get_gitdiff(filenode_old, filenode_new,
77 f_gitdiff = get_gitdiff(filenode_old, filenode_new,
78 ignore_whitespace=ignore_whitespace,
78 ignore_whitespace=ignore_whitespace,
79 context=line_context)
79 context=line_context)
80 diff_processor = DiffProcessor(f_gitdiff, format='gitdiff')
80 diff_processor = DiffProcessor(f_gitdiff, format='gitdiff')
81
81
82 diff = diff_processor.as_html(enable_comments=enable_comments)
82 diff = diff_processor.as_html(enable_comments=enable_comments)
83 stats = diff_processor.stat()
83 stats = diff_processor.stat()
84 size = len(diff or '')
84 size = len(diff or '')
85 else:
85 else:
86 diff = wrap_to_table(_('Changeset was too big and was cut off, use '
86 diff = wrap_to_table(_('Changeset was too big and was cut off, use '
87 'diff menu to display this diff'))
87 'diff menu to display this diff'))
88 stats = (0, 0)
88 stats = (0, 0)
89 size = 0
89 size = 0
90 if not diff:
90 if not diff:
91 submodules = filter(lambda o: isinstance(o, SubModuleNode),
91 submodules = filter(lambda o: isinstance(o, SubModuleNode),
92 [filenode_new, filenode_old])
92 [filenode_new, filenode_old])
93 if submodules:
93 if submodules:
94 diff = wrap_to_table(escape('Submodule %r' % submodules[0]))
94 diff = wrap_to_table(escape('Submodule %r' % submodules[0]))
95 else:
95 else:
96 diff = wrap_to_table(_('No changes detected'))
96 diff = wrap_to_table(_('No changes detected'))
97
97
98 cs1 = filenode_old.changeset.raw_id
98 cs1 = filenode_old.changeset.raw_id
99 cs2 = filenode_new.changeset.raw_id
99 cs2 = filenode_new.changeset.raw_id
100
100
101 return size, cs1, cs2, diff, stats
101 return size, cs1, cs2, diff, stats
102
102
103
103
104 def get_gitdiff(filenode_old, filenode_new, ignore_whitespace=True, context=3):
104 def get_gitdiff(filenode_old, filenode_new, ignore_whitespace=True, context=3):
105 """
105 """
106 Returns git style diff between given ``filenode_old`` and ``filenode_new``.
106 Returns git style diff between given ``filenode_old`` and ``filenode_new``.
107
107
108 :param ignore_whitespace: ignore whitespaces in diff
108 :param ignore_whitespace: ignore whitespaces in diff
109 """
109 """
110 # make sure we pass in default context
110 # make sure we pass in default context
111 context = context or 3
111 context = context or 3
112 submodules = filter(lambda o: isinstance(o, SubModuleNode),
112 submodules = filter(lambda o: isinstance(o, SubModuleNode),
113 [filenode_new, filenode_old])
113 [filenode_new, filenode_old])
114 if submodules:
114 if submodules:
115 return ''
115 return ''
116
116
117 for filenode in (filenode_old, filenode_new):
117 for filenode in (filenode_old, filenode_new):
118 if not isinstance(filenode, FileNode):
118 if not isinstance(filenode, FileNode):
119 raise VCSError("Given object should be FileNode object, not %s"
119 raise VCSError("Given object should be FileNode object, not %s"
120 % filenode.__class__)
120 % filenode.__class__)
121
121
122 repo = filenode_new.changeset.repository
122 repo = filenode_new.changeset.repository
123 old_raw_id = getattr(filenode_old.changeset, 'raw_id', repo.EMPTY_CHANGESET)
123 old_raw_id = getattr(filenode_old.changeset, 'raw_id', repo.EMPTY_CHANGESET)
124 new_raw_id = getattr(filenode_new.changeset, 'raw_id', repo.EMPTY_CHANGESET)
124 new_raw_id = getattr(filenode_new.changeset, 'raw_id', repo.EMPTY_CHANGESET)
125
125
126 vcs_gitdiff = repo.get_diff(old_raw_id, new_raw_id, filenode_new.path,
126 vcs_gitdiff = repo.get_diff(old_raw_id, new_raw_id, filenode_new.path,
127 ignore_whitespace, context)
127 ignore_whitespace, context)
128 return vcs_gitdiff
128 return vcs_gitdiff
129
129
130
130
131 class DiffProcessor(object):
131 class DiffProcessor(object):
132 """
132 """
133 Give it a unified diff and it returns a list of the files that were
133 Give it a unified diff and it returns a list of the files that were
134 mentioned in the diff together with a dict of meta information that
134 mentioned in the diff together with a dict of meta information that
135 can be used to render it in a HTML template.
135 can be used to render it in a HTML template.
136 """
136 """
137 _chunk_re = re.compile(r'@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@(.*)')
137 _chunk_re = re.compile(r'@@ -(\d+)(?:,(\d+))? \+(\d+)(?:,(\d+))? @@(.*)')
138 _newline_marker = '\\ No newline at end of file\n'
138 _newline_marker = '\\ No newline at end of file\n'
139
139
140 def __init__(self, diff, differ='diff', format='gitdiff'):
140 def __init__(self, diff, differ='diff', format='gitdiff'):
141 """
141 """
142 :param diff: a text in diff format or generator
142 :param diff: a text in diff format or generator
143 :param format: format of diff passed, `udiff` or `gitdiff`
143 :param format: format of diff passed, `udiff` or `gitdiff`
144 """
144 """
145 if isinstance(diff, basestring):
145 if isinstance(diff, basestring):
146 diff = [diff]
146 diff = [diff]
147
147
148 self.__udiff = diff
148 self.__udiff = diff
149 self.__format = format
149 self.__format = format
150 self.adds = 0
150 self.adds = 0
151 self.removes = 0
151 self.removes = 0
152
152
153 if isinstance(self.__udiff, basestring):
153 if isinstance(self.__udiff, basestring):
154 self.lines = iter(self.__udiff.splitlines(1))
154 self.lines = iter(self.__udiff.splitlines(1))
155
155
156 elif self.__format == 'gitdiff':
156 elif self.__format == 'gitdiff':
157 udiff_copy = self.copy_iterator()
157 udiff_copy = self.copy_iterator()
158 self.lines = imap(self.escaper, self._parse_gitdiff(udiff_copy))
158 self.lines = imap(self.escaper, self._parse_gitdiff(udiff_copy))
159 else:
159 else:
160 udiff_copy = self.copy_iterator()
160 udiff_copy = self.copy_iterator()
161 self.lines = imap(self.escaper, udiff_copy)
161 self.lines = imap(self.escaper, udiff_copy)
162
162
163 # Select a differ.
163 # Select a differ.
164 if differ == 'difflib':
164 if differ == 'difflib':
165 self.differ = self._highlight_line_difflib
165 self.differ = self._highlight_line_difflib
166 else:
166 else:
167 self.differ = self._highlight_line_udiff
167 self.differ = self._highlight_line_udiff
168
168
169 def escaper(self, string):
169 def escaper(self, string):
170 return markupsafe.escape(string)
170 return markupsafe.escape(string)
171
171
172 def copy_iterator(self):
172 def copy_iterator(self):
173 """
173 """
174 make a fresh copy of generator, we should not iterate thru
174 make a fresh copy of generator, we should not iterate thru
175 an original as it's needed for repeating operations on
175 an original as it's needed for repeating operations on
176 this instance of DiffProcessor
176 this instance of DiffProcessor
177 """
177 """
178 self.__udiff, iterator_copy = tee(self.__udiff)
178 self.__udiff, iterator_copy = tee(self.__udiff)
179 return iterator_copy
179 return iterator_copy
180
180
181 def _extract_rev(self, line1, line2):
181 def _extract_rev(self, line1, line2):
182 """
182 """
183 Extract the operation (A/M/D), filename and revision hint from a line.
183 Extract the operation (A/M/D), filename and revision hint from a line.
184 """
184 """
185
185
186 try:
186 try:
187 if line1.startswith('--- ') and line2.startswith('+++ '):
187 if line1.startswith('--- ') and line2.startswith('+++ '):
188 l1 = line1[4:].split(None, 1)
188 l1 = line1[4:].split(None, 1)
189 old_filename = (l1[0].replace('a/', '', 1)
189 old_filename = (l1[0].replace('a/', '', 1)
190 if len(l1) >= 1 else None)
190 if len(l1) >= 1 else None)
191 old_rev = l1[1] if len(l1) == 2 else 'old'
191 old_rev = l1[1] if len(l1) == 2 else 'old'
192
192
193 l2 = line2[4:].split(None, 1)
193 l2 = line2[4:].split(None, 1)
194 new_filename = (l2[0].replace('b/', '', 1)
194 new_filename = (l2[0].replace('b/', '', 1)
195 if len(l1) >= 1 else None)
195 if len(l1) >= 1 else None)
196 new_rev = l2[1] if len(l2) == 2 else 'new'
196 new_rev = l2[1] if len(l2) == 2 else 'new'
197
197
198 filename = (old_filename
198 filename = (old_filename
199 if old_filename != '/dev/null' else new_filename)
199 if old_filename != '/dev/null' else new_filename)
200
200
201 operation = 'D' if new_filename == '/dev/null' else None
201 operation = 'D' if new_filename == '/dev/null' else None
202 if not operation:
202 if not operation:
203 operation = 'M' if old_filename != '/dev/null' else 'A'
203 operation = 'M' if old_filename != '/dev/null' else 'A'
204
204
205 return operation, filename, new_rev, old_rev
205 return operation, filename, new_rev, old_rev
206 except (ValueError, IndexError):
206 except (ValueError, IndexError):
207 pass
207 pass
208
208
209 return None, None, None, None
209 return None, None, None, None
210
210
211 def _parse_gitdiff(self, diffiterator):
211 def _parse_gitdiff(self, diffiterator):
212 def line_decoder(l):
212 def line_decoder(l):
213 if l.startswith('+') and not l.startswith('+++'):
213 if l.startswith('+') and not l.startswith('+++'):
214 self.adds += 1
214 self.adds += 1
215 elif l.startswith('-') and not l.startswith('---'):
215 elif l.startswith('-') and not l.startswith('---'):
216 self.removes += 1
216 self.removes += 1
217 return l.decode('utf8', 'replace')
217 return l.decode('utf8', 'replace')
218
218
219 output = list(diffiterator)
219 output = list(diffiterator)
220 size = len(output)
220 size = len(output)
221
221
222 if size == 2:
222 if size == 2:
223 l = []
223 l = []
224 l.extend([output[0]])
224 l.extend([output[0]])
225 l.extend(output[1].splitlines(1))
225 l.extend(output[1].splitlines(1))
226 return map(line_decoder, l)
226 return map(line_decoder, l)
227 elif size == 1:
227 elif size == 1:
228 return map(line_decoder, output[0].splitlines(1))
228 return map(line_decoder, output[0].splitlines(1))
229 elif size == 0:
229 elif size == 0:
230 return []
230 return []
231
231
232 raise Exception('wrong size of diff %s' % size)
232 raise Exception('wrong size of diff %s' % size)
233
233
234 def _highlight_line_difflib(self, line, next_):
234 def _highlight_line_difflib(self, line, next_):
235 """
235 """
236 Highlight inline changes in both lines.
236 Highlight inline changes in both lines.
237 """
237 """
238
238
239 if line['action'] == 'del':
239 if line['action'] == 'del':
240 old, new = line, next_
240 old, new = line, next_
241 else:
241 else:
242 old, new = next_, line
242 old, new = next_, line
243
243
244 oldwords = re.split(r'(\W)', old['line'])
244 oldwords = re.split(r'(\W)', old['line'])
245 newwords = re.split(r'(\W)', new['line'])
245 newwords = re.split(r'(\W)', new['line'])
246
246
247 sequence = difflib.SequenceMatcher(None, oldwords, newwords)
247 sequence = difflib.SequenceMatcher(None, oldwords, newwords)
248
248
249 oldfragments, newfragments = [], []
249 oldfragments, newfragments = [], []
250 for tag, i1, i2, j1, j2 in sequence.get_opcodes():
250 for tag, i1, i2, j1, j2 in sequence.get_opcodes():
251 oldfrag = ''.join(oldwords[i1:i2])
251 oldfrag = ''.join(oldwords[i1:i2])
252 newfrag = ''.join(newwords[j1:j2])
252 newfrag = ''.join(newwords[j1:j2])
253 if tag != 'equal':
253 if tag != 'equal':
254 if oldfrag:
254 if oldfrag:
255 oldfrag = '<del>%s</del>' % oldfrag
255 oldfrag = '<del>%s</del>' % oldfrag
256 if newfrag:
256 if newfrag:
257 newfrag = '<ins>%s</ins>' % newfrag
257 newfrag = '<ins>%s</ins>' % newfrag
258 oldfragments.append(oldfrag)
258 oldfragments.append(oldfrag)
259 newfragments.append(newfrag)
259 newfragments.append(newfrag)
260
260
261 old['line'] = "".join(oldfragments)
261 old['line'] = "".join(oldfragments)
262 new['line'] = "".join(newfragments)
262 new['line'] = "".join(newfragments)
263
263
264 def _highlight_line_udiff(self, line, next_):
264 def _highlight_line_udiff(self, line, next_):
265 """
265 """
266 Highlight inline changes in both lines.
266 Highlight inline changes in both lines.
267 """
267 """
268 start = 0
268 start = 0
269 limit = min(len(line['line']), len(next_['line']))
269 limit = min(len(line['line']), len(next_['line']))
270 while start < limit and line['line'][start] == next_['line'][start]:
270 while start < limit and line['line'][start] == next_['line'][start]:
271 start += 1
271 start += 1
272 end = -1
272 end = -1
273 limit -= start
273 limit -= start
274 while -end <= limit and line['line'][end] == next_['line'][end]:
274 while -end <= limit and line['line'][end] == next_['line'][end]:
275 end -= 1
275 end -= 1
276 end += 1
276 end += 1
277 if start or end:
277 if start or end:
278 def do(l):
278 def do(l):
279 last = end + len(l['line'])
279 last = end + len(l['line'])
280 if l['action'] == 'add':
280 if l['action'] == 'add':
281 tag = 'ins'
281 tag = 'ins'
282 else:
282 else:
283 tag = 'del'
283 tag = 'del'
284 l['line'] = '%s<%s>%s</%s>%s' % (
284 l['line'] = '%s<%s>%s</%s>%s' % (
285 l['line'][:start],
285 l['line'][:start],
286 tag,
286 tag,
287 l['line'][start:last],
287 l['line'][start:last],
288 tag,
288 tag,
289 l['line'][last:]
289 l['line'][last:]
290 )
290 )
291 do(line)
291 do(line)
292 do(next_)
292 do(next_)
293
293
294 def _parse_udiff(self, inline_diff=True):
294 def _parse_udiff(self, inline_diff=True):
295 """
295 """
296 Parse the diff an return data for the template.
296 Parse the diff an return data for the template.
297 """
297 """
298 lineiter = self.lines
298 lineiter = self.lines
299 files = []
299 files = []
300 try:
300 try:
301 line = lineiter.next()
301 line = lineiter.next()
302 while 1:
302 while 1:
303 # continue until we found the old file
303 # continue until we found the old file
304 if not line.startswith('--- '):
304 if not line.startswith('--- '):
305 line = lineiter.next()
305 line = lineiter.next()
306 continue
306 continue
307
307
308 chunks = []
308 chunks = []
309 stats = [0, 0]
309 stats = [0, 0]
310 operation, filename, old_rev, new_rev = \
310 operation, filename, old_rev, new_rev = \
311 self._extract_rev(line, lineiter.next())
311 self._extract_rev(line, lineiter.next())
312 files.append({
312 files.append({
313 'filename': filename,
313 'filename': filename,
314 'old_revision': old_rev,
314 'old_revision': old_rev,
315 'new_revision': new_rev,
315 'new_revision': new_rev,
316 'chunks': chunks,
316 'chunks': chunks,
317 'operation': operation,
317 'operation': operation,
318 'stats': stats,
318 'stats': stats,
319 })
319 })
320
320
321 line = lineiter.next()
321 line = lineiter.next()
322 while line:
322 while line:
323 match = self._chunk_re.match(line)
323 match = self._chunk_re.match(line)
324 if not match:
324 if not match:
325 break
325 break
326
326
327 lines = []
327 lines = []
328 chunks.append(lines)
328 chunks.append(lines)
329
329
330 old_line, old_end, new_line, new_end = \
330 old_line, old_end, new_line, new_end = \
331 [int(x or 1) for x in match.groups()[:-1]]
331 [int(x or 1) for x in match.groups()[:-1]]
332 old_line -= 1
332 old_line -= 1
333 new_line -= 1
333 new_line -= 1
334 gr = match.groups()
334 gr = match.groups()
335 context = len(gr) == 5
335 context = len(gr) == 5
336 old_end += old_line
336 old_end += old_line
337 new_end += new_line
337 new_end += new_line
338
338
339 if context:
339 if context:
340 # skip context only if it's first line
340 # skip context only if it's first line
341 if int(gr[0]) > 1:
341 if int(gr[0]) > 1:
342 lines.append({
342 lines.append({
343 'old_lineno': '...',
343 'old_lineno': '...',
344 'new_lineno': '...',
344 'new_lineno': '...',
345 'action': 'context',
345 'action': 'context',
346 'line': line,
346 'line': line,
347 })
347 })
348
348
349 line = lineiter.next()
349 line = lineiter.next()
350
350
351 while old_line < old_end or new_line < new_end:
351 while old_line < old_end or new_line < new_end:
352 if line:
352 if line:
353 command = line[0]
353 command = line[0]
354 if command in ['+', '-', ' ']:
354 if command in ['+', '-', ' ']:
355 #only modify the line if it's actually a diff
355 #only modify the line if it's actually a diff
356 # thing
356 # thing
357 line = line[1:]
357 line = line[1:]
358 else:
358 else:
359 command = ' '
359 command = ' '
360
360
361 affects_old = affects_new = False
361 affects_old = affects_new = False
362
362
363 # ignore those if we don't expect them
363 # ignore those if we don't expect them
364 if command in '#@':
364 if command in '#@':
365 continue
365 continue
366 elif command == '+':
366 elif command == '+':
367 affects_new = True
367 affects_new = True
368 action = 'add'
368 action = 'add'
369 stats[0] += 1
369 stats[0] += 1
370 elif command == '-':
370 elif command == '-':
371 affects_old = True
371 affects_old = True
372 action = 'del'
372 action = 'del'
373 stats[1] += 1
373 stats[1] += 1
374 else:
374 else:
375 affects_old = affects_new = True
375 affects_old = affects_new = True
376 action = 'unmod'
376 action = 'unmod'
377
377
378 if line != self._newline_marker:
378 if line != self._newline_marker:
379 old_line += affects_old
379 old_line += affects_old
380 new_line += affects_new
380 new_line += affects_new
381 lines.append({
381 lines.append({
382 'old_lineno': affects_old and old_line or '',
382 'old_lineno': affects_old and old_line or '',
383 'new_lineno': affects_new and new_line or '',
383 'new_lineno': affects_new and new_line or '',
384 'action': action,
384 'action': action,
385 'line': line
385 'line': line
386 })
386 })
387
387
388 line = lineiter.next()
388 line = lineiter.next()
389 if line == self._newline_marker:
389 if line == self._newline_marker:
390 # we need to append to lines, since this is not
390 # we need to append to lines, since this is not
391 # counted in the line specs of diff
391 # counted in the line specs of diff
392 lines.append({
392 lines.append({
393 'old_lineno': '...',
393 'old_lineno': '...',
394 'new_lineno': '...',
394 'new_lineno': '...',
395 'action': 'context',
395 'action': 'context',
396 'line': line
396 'line': line
397 })
397 })
398
398
399 except StopIteration:
399 except StopIteration:
400 pass
400 pass
401
401
402 sorter = lambda info: {'A': 0, 'M': 1, 'D': 2}.get(info['operation'])
402 sorter = lambda info: {'A': 0, 'M': 1, 'D': 2}.get(info['operation'])
403 if inline_diff is False:
403 if inline_diff is False:
404 return sorted(files, key=sorter)
404 return sorted(files, key=sorter)
405
405
406 # highlight inline changes
406 # highlight inline changes
407 for diff_data in files:
407 for diff_data in files:
408 for chunk in diff_data['chunks']:
408 for chunk in diff_data['chunks']:
409 lineiter = iter(chunk)
409 lineiter = iter(chunk)
410 try:
410 try:
411 while 1:
411 while 1:
412 line = lineiter.next()
412 line = lineiter.next()
413 if line['action'] not in ['unmod', 'context']:
413 if line['action'] not in ['unmod', 'context']:
414 nextline = lineiter.next()
414 nextline = lineiter.next()
415 if nextline['action'] in ['unmod', 'context'] or \
415 if nextline['action'] in ['unmod', 'context'] or \
416 nextline['action'] == line['action']:
416 nextline['action'] == line['action']:
417 continue
417 continue
418 self.differ(line, nextline)
418 self.differ(line, nextline)
419 except StopIteration:
419 except StopIteration:
420 pass
420 pass
421
421
422 return sorted(files, key=sorter)
422 return sorted(files, key=sorter)
423
423
424 def prepare(self, inline_diff=True):
424 def prepare(self, inline_diff=True):
425 """
425 """
426 Prepare the passed udiff for HTML rendering. It'l return a list
426 Prepare the passed udiff for HTML rendering. It'l return a list
427 of dicts
427 of dicts
428 """
428 """
429 return self._parse_udiff(inline_diff=inline_diff)
429 return self._parse_udiff(inline_diff=inline_diff)
430
430
431 def _safe_id(self, idstring):
431 def _safe_id(self, idstring):
432 """Make a string safe for including in an id attribute.
432 """Make a string safe for including in an id attribute.
433
433
434 The HTML spec says that id attributes 'must begin with
434 The HTML spec says that id attributes 'must begin with
435 a letter ([A-Za-z]) and may be followed by any number
435 a letter ([A-Za-z]) and may be followed by any number
436 of letters, digits ([0-9]), hyphens ("-"), underscores
436 of letters, digits ([0-9]), hyphens ("-"), underscores
437 ("_"), colons (":"), and periods (".")'. These regexps
437 ("_"), colons (":"), and periods (".")'. These regexps
438 are slightly over-zealous, in that they remove colons
438 are slightly over-zealous, in that they remove colons
439 and periods unnecessarily.
439 and periods unnecessarily.
440
440
441 Whitespace is transformed into underscores, and then
441 Whitespace is transformed into underscores, and then
442 anything which is not a hyphen or a character that
442 anything which is not a hyphen or a character that
443 matches \w (alphanumerics and underscore) is removed.
443 matches \w (alphanumerics and underscore) is removed.
444
444
445 """
445 """
446 # Transform all whitespace to underscore
446 # Transform all whitespace to underscore
447 idstring = re.sub(r'\s', "_", '%s' % idstring)
447 idstring = re.sub(r'\s', "_", '%s' % idstring)
448 # Remove everything that is not a hyphen or a member of \w
448 # Remove everything that is not a hyphen or a member of \w
449 idstring = re.sub(r'(?!-)\W', "", idstring).lower()
449 idstring = re.sub(r'(?!-)\W', "", idstring).lower()
450 return idstring
450 return idstring
451
451
452 def raw_diff(self):
452 def raw_diff(self):
453 """
453 """
454 Returns raw string as udiff
454 Returns raw string as udiff
455 """
455 """
456 udiff_copy = self.copy_iterator()
456 udiff_copy = self.copy_iterator()
457 if self.__format == 'gitdiff':
457 if self.__format == 'gitdiff':
458 udiff_copy = self._parse_gitdiff(udiff_copy)
458 udiff_copy = self._parse_gitdiff(udiff_copy)
459 return u''.join(udiff_copy)
459 return u''.join(udiff_copy)
460
460
461 def as_html(self, table_class='code-difftable', line_class='line',
461 def as_html(self, table_class='code-difftable', line_class='line',
462 new_lineno_class='lineno old', old_lineno_class='lineno new',
462 new_lineno_class='lineno old', old_lineno_class='lineno new',
463 code_class='code', enable_comments=False, diff_lines=None):
463 code_class='code', enable_comments=False, diff_lines=None):
464 """
464 """
465 Return given diff as html table with customized css classes
465 Return given diff as html table with customized css classes
466 """
466 """
467 def _link_to_if(condition, label, url):
467 def _link_to_if(condition, label, url):
468 """
468 """
469 Generates a link if condition is meet or just the label if not.
469 Generates a link if condition is meet or just the label if not.
470 """
470 """
471
471
472 if condition:
472 if condition:
473 return '''<a href="%(url)s">%(label)s</a>''' % {
473 return '''<a href="%(url)s">%(label)s</a>''' % {
474 'url': url,
474 'url': url,
475 'label': label
475 'label': label
476 }
476 }
477 else:
477 else:
478 return label
478 return label
479 if diff_lines is None:
479 if diff_lines is None:
480 diff_lines = self.prepare()
480 diff_lines = self.prepare()
481 _html_empty = True
481 _html_empty = True
482 _html = []
482 _html = []
483 _html.append('''<table class="%(table_class)s">\n''' % {
483 _html.append('''<table class="%(table_class)s">\n''' % {
484 'table_class': table_class
484 'table_class': table_class
485 })
485 })
486 for diff in diff_lines:
486 for diff in diff_lines:
487 for line in diff['chunks']:
487 for line in diff['chunks']:
488 _html_empty = False
488 _html_empty = False
489 for change in line:
489 for change in line:
490 _html.append('''<tr class="%(lc)s %(action)s">\n''' % {
490 _html.append('''<tr class="%(lc)s %(action)s">\n''' % {
491 'lc': line_class,
491 'lc': line_class,
492 'action': change['action']
492 'action': change['action']
493 })
493 })
494 anchor_old_id = ''
494 anchor_old_id = ''
495 anchor_new_id = ''
495 anchor_new_id = ''
496 anchor_old = "%(filename)s_o%(oldline_no)s" % {
496 anchor_old = "%(filename)s_o%(oldline_no)s" % {
497 'filename': self._safe_id(diff['filename']),
497 'filename': self._safe_id(diff['filename']),
498 'oldline_no': change['old_lineno']
498 'oldline_no': change['old_lineno']
499 }
499 }
500 anchor_new = "%(filename)s_n%(oldline_no)s" % {
500 anchor_new = "%(filename)s_n%(oldline_no)s" % {
501 'filename': self._safe_id(diff['filename']),
501 'filename': self._safe_id(diff['filename']),
502 'oldline_no': change['new_lineno']
502 'oldline_no': change['new_lineno']
503 }
503 }
504 cond_old = (change['old_lineno'] != '...' and
504 cond_old = (change['old_lineno'] != '...' and
505 change['old_lineno'])
505 change['old_lineno'])
506 cond_new = (change['new_lineno'] != '...' and
506 cond_new = (change['new_lineno'] != '...' and
507 change['new_lineno'])
507 change['new_lineno'])
508 if cond_old:
508 if cond_old:
509 anchor_old_id = 'id="%s"' % anchor_old
509 anchor_old_id = 'id="%s"' % anchor_old
510 if cond_new:
510 if cond_new:
511 anchor_new_id = 'id="%s"' % anchor_new
511 anchor_new_id = 'id="%s"' % anchor_new
512 ###########################################################
512 ###########################################################
513 # OLD LINE NUMBER
513 # OLD LINE NUMBER
514 ###########################################################
514 ###########################################################
515 _html.append('''\t<td %(a_id)s class="%(olc)s">''' % {
515 _html.append('''\t<td %(a_id)s class="%(olc)s">''' % {
516 'a_id': anchor_old_id,
516 'a_id': anchor_old_id,
517 'olc': old_lineno_class
517 'olc': old_lineno_class
518 })
518 })
519
519
520 _html.append('''%(link)s''' % {
520 _html.append('''%(link)s''' % {
521 'link': _link_to_if(True, change['old_lineno'],
521 'link': _link_to_if(True, change['old_lineno'],
522 '#%s' % anchor_old)
522 '#%s' % anchor_old)
523 })
523 })
524 _html.append('''</td>\n''')
524 _html.append('''</td>\n''')
525 ###########################################################
525 ###########################################################
526 # NEW LINE NUMBER
526 # NEW LINE NUMBER
527 ###########################################################
527 ###########################################################
528
528
529 _html.append('''\t<td %(a_id)s class="%(nlc)s">''' % {
529 _html.append('''\t<td %(a_id)s class="%(nlc)s">''' % {
530 'a_id': anchor_new_id,
530 'a_id': anchor_new_id,
531 'nlc': new_lineno_class
531 'nlc': new_lineno_class
532 })
532 })
533
533
534 _html.append('''%(link)s''' % {
534 _html.append('''%(link)s''' % {
535 'link': _link_to_if(True, change['new_lineno'],
535 'link': _link_to_if(True, change['new_lineno'],
536 '#%s' % anchor_new)
536 '#%s' % anchor_new)
537 })
537 })
538 _html.append('''</td>\n''')
538 _html.append('''</td>\n''')
539 ###########################################################
539 ###########################################################
540 # CODE
540 # CODE
541 ###########################################################
541 ###########################################################
542 comments = '' if enable_comments else 'no-comment'
542 comments = '' if enable_comments else 'no-comment'
543 _html.append('''\t<td class="%(cc)s %(inc)s">''' % {
543 _html.append('''\t<td class="%(cc)s %(inc)s">''' % {
544 'cc': code_class,
544 'cc': code_class,
545 'inc': comments
545 'inc': comments
546 })
546 })
547 _html.append('''\n\t\t<pre>%(code)s</pre>\n''' % {
547 _html.append('''\n\t\t<pre>%(code)s</pre>\n''' % {
548 'code': change['line']
548 'code': change['line']
549 })
549 })
550 _html.append('''\t</td>''')
550 _html.append('''\t</td>''')
551 _html.append('''\n</tr>\n''')
551 _html.append('''\n</tr>\n''')
552 _html.append('''</table>''')
552 _html.append('''</table>''')
553 if _html_empty:
553 if _html_empty:
554 return None
554 return None
555 return ''.join(_html)
555 return ''.join(_html)
556
556
557 def stat(self):
557 def stat(self):
558 """
558 """
559 Returns tuple of added, and removed lines for this instance
559 Returns tuple of added, and removed lines for this instance
560 """
560 """
561 return self.adds, self.removes
561 return self.adds, self.removes
562
562
563
563
564 class InMemoryBundleRepo(bundlerepository):
564 class InMemoryBundleRepo(bundlerepository):
565 def __init__(self, ui, path, bundlestream):
565 def __init__(self, ui, path, bundlestream):
566 self._tempparent = None
566 self._tempparent = None
567 localrepo.localrepository.__init__(self, ui, path)
567 localrepo.localrepository.__init__(self, ui, path)
568 self.ui.setconfig('phases', 'publish', False)
568 self.ui.setconfig('phases', 'publish', False)
569
569
570 self.bundle = bundlestream
570 self.bundle = bundlestream
571
571
572 # dict with the mapping 'filename' -> position in the bundle
572 # dict with the mapping 'filename' -> position in the bundle
573 self.bundlefilespos = {}
573 self.bundlefilespos = {}
574
574
575
575
576 def differ(org_repo, org_ref, other_repo, other_ref, discovery_data=None):
576 def differ(org_repo, org_ref, other_repo, other_ref, discovery_data=None):
577 """
577 """
578 General differ between branches, bookmarks or separate but releated
578 General differ between branches, bookmarks or separate but releated
579 repositories
579 repositories
580
580
581 :param org_repo:
581 :param org_repo:
582 :type org_repo:
582 :type org_repo:
583 :param org_ref:
583 :param org_ref:
584 :type org_ref:
584 :type org_ref:
585 :param other_repo:
585 :param other_repo:
586 :type other_repo:
586 :type other_repo:
587 :param other_ref:
587 :param other_ref:
588 :type other_ref:
588 :type other_ref:
589 """
589 """
590
590
591 bundlerepo = None
591 bundlerepo = None
592 ignore_whitespace = False
592 ignore_whitespace = False
593 context = 3
593 context = 3
594 org_repo = org_repo.scm_instance._repo
594 org_repo = org_repo.scm_instance._repo
595 other_repo = other_repo.scm_instance._repo
595 other_repo = other_repo.scm_instance._repo
596 opts = diffopts(git=True, ignorews=ignore_whitespace, context=context)
596 opts = diffopts(git=True, ignorews=ignore_whitespace, context=context)
597 org_ref = org_ref[1]
597 org_ref = org_ref[1]
598 other_ref = other_ref[1]
598 other_ref = other_ref[1]
599
599
600 if org_repo != other_repo:
600 if org_repo != other_repo:
601
601
602 common, incoming, rheads = discovery_data
602 common, incoming, rheads = discovery_data
603 other_repo_peer = localrepo.locallegacypeer(other_repo.local())
603 other_repo_peer = localrepo.locallegacypeer(other_repo.local())
604 # create a bundle (uncompressed if other repo is not local)
604 # create a bundle (uncompressed if other repo is not local)
605 if other_repo_peer.capable('getbundle') and incoming:
605 if other_repo_peer.capable('getbundle') and incoming:
606 # disable repo hooks here since it's just bundle !
606 # disable repo hooks here since it's just bundle !
607 # patch and reset hooks section of UI config to not run any
607 # patch and reset hooks section of UI config to not run any
608 # hooks on fetching archives with subrepos
608 # hooks on fetching archives with subrepos
609 for k, _ in other_repo.ui.configitems('hooks'):
609 for k, _ in other_repo.ui.configitems('hooks'):
610 other_repo.ui.setconfig('hooks', k, None)
610 other_repo.ui.setconfig('hooks', k, None)
611
611
612 unbundle = other_repo.getbundle('incoming', common=common,
612 unbundle = other_repo.getbundle('incoming', common=common,
613 heads=rheads)
613 heads=None)
614
614
615 buf = BytesIO()
615 buf = BytesIO()
616 while True:
616 while True:
617 chunk = unbundle._stream.read(1024 * 4)
617 chunk = unbundle._stream.read(1024 * 4)
618 if not chunk:
618 if not chunk:
619 break
619 break
620 buf.write(chunk)
620 buf.write(chunk)
621
621
622 buf.seek(0)
622 buf.seek(0)
623 # replace chunked _stream with data that can do tell() and seek()
623 # replace chunked _stream with data that can do tell() and seek()
624 unbundle._stream = buf
624 unbundle._stream = buf
625
625
626 ui = make_ui('db')
626 ui = make_ui('db')
627 bundlerepo = InMemoryBundleRepo(ui, path=org_repo.root,
627 bundlerepo = InMemoryBundleRepo(ui, path=org_repo.root,
628 bundlestream=unbundle)
628 bundlestream=unbundle)
629
629
630 return ''.join(patch.diff(bundlerepo or org_repo,
630 return ''.join(patch.diff(bundlerepo or org_repo,
631 node1=org_repo[org_ref].node(),
631 node1=org_repo[org_ref].node(),
632 node2=other_repo[other_ref].node(),
632 node2=other_repo[other_ref].node(),
633 opts=opts))
633 opts=opts))
634 else:
634 else:
635 return ''.join(patch.diff(org_repo, node1=org_ref, node2=other_ref,
635 return ''.join(patch.diff(org_repo, node1=org_ref, node2=other_ref,
636 opts=opts))
636 opts=opts))
@@ -1,17 +1,18 b''
1 """
1 """
2 Mercurial libs compatibility
2 Mercurial libs compatibility
3 """
3 """
4
4
5 from mercurial import archival, merge as hg_merge, patch, ui
5 from mercurial import archival, merge as hg_merge, patch, ui
6 from mercurial.commands import clone, nullid, pull
6 from mercurial.commands import clone, nullid, pull
7 from mercurial.context import memctx, memfilectx
7 from mercurial.context import memctx, memfilectx
8 from mercurial.error import RepoError, RepoLookupError, Abort
8 from mercurial.error import RepoError, RepoLookupError, Abort
9 from mercurial.hgweb.common import get_contact
9 from mercurial.hgweb.common import get_contact
10 from mercurial.localrepo import localrepository
10 from mercurial.localrepo import localrepository
11 from mercurial.match import match
11 from mercurial.match import match
12 from mercurial.mdiff import diffopts
12 from mercurial.mdiff import diffopts
13 from mercurial.node import hex
13 from mercurial.node import hex
14 from mercurial.encoding import tolocal
14 from mercurial.encoding import tolocal
15 from mercurial import discovery
15 from mercurial import discovery
16 from mercurial import localrepo
16 from mercurial import localrepo
17 from mercurial import scmutil No newline at end of file
17 from mercurial import scmutil
18 from mercurial.discovery import findcommonoutgoing No newline at end of file
@@ -1,257 +1,260 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.pull_request
3 rhodecode.model.pull_request
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 pull request model for RhodeCode
6 pull request model for RhodeCode
7
7
8 :created_on: Jun 6, 2012
8 :created_on: Jun 6, 2012
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2012-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2012-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import logging
26 import logging
27 import binascii
27 import binascii
28 import datetime
28 import datetime
29
29
30 from pylons.i18n.translation import _
30 from pylons.i18n.translation import _
31
31
32 from rhodecode.model.meta import Session
32 from rhodecode.model.meta import Session
33 from rhodecode.lib import helpers as h
33 from rhodecode.lib import helpers as h
34 from rhodecode.model import BaseModel
34 from rhodecode.model import BaseModel
35 from rhodecode.model.db import PullRequest, PullRequestReviewers, Notification
35 from rhodecode.model.db import PullRequest, PullRequestReviewers, Notification
36 from rhodecode.model.notification import NotificationModel
36 from rhodecode.model.notification import NotificationModel
37 from rhodecode.lib.utils2 import safe_unicode
37 from rhodecode.lib.utils2 import safe_unicode
38
38
39 from rhodecode.lib.vcs.utils.hgcompat import discovery, localrepo, scmutil
39 from rhodecode.lib.vcs.utils.hgcompat import discovery, localrepo, scmutil, \
40 findcommonoutgoing
40
41
41 log = logging.getLogger(__name__)
42 log = logging.getLogger(__name__)
42
43
43
44
44 class PullRequestModel(BaseModel):
45 class PullRequestModel(BaseModel):
45
46
46 cls = PullRequest
47 cls = PullRequest
47
48
48 def __get_pull_request(self, pull_request):
49 def __get_pull_request(self, pull_request):
49 return self._get_instance(PullRequest, pull_request)
50 return self._get_instance(PullRequest, pull_request)
50
51
51 def get_all(self, repo):
52 def get_all(self, repo):
52 repo = self._get_repo(repo)
53 repo = self._get_repo(repo)
53 return PullRequest.query().filter(PullRequest.other_repo == repo).all()
54 return PullRequest.query().filter(PullRequest.other_repo == repo).all()
54
55
55 def create(self, created_by, org_repo, org_ref, other_repo,
56 def create(self, created_by, org_repo, org_ref, other_repo,
56 other_ref, revisions, reviewers, title, description=None):
57 other_ref, revisions, reviewers, title, description=None):
57
58
58 created_by_user = self._get_user(created_by)
59 created_by_user = self._get_user(created_by)
59 org_repo = self._get_repo(org_repo)
60 org_repo = self._get_repo(org_repo)
60 other_repo = self._get_repo(other_repo)
61 other_repo = self._get_repo(other_repo)
61
62
62 new = PullRequest()
63 new = PullRequest()
63 new.org_repo = org_repo
64 new.org_repo = org_repo
64 new.org_ref = org_ref
65 new.org_ref = org_ref
65 new.other_repo = other_repo
66 new.other_repo = other_repo
66 new.other_ref = other_ref
67 new.other_ref = other_ref
67 new.revisions = revisions
68 new.revisions = revisions
68 new.title = title
69 new.title = title
69 new.description = description
70 new.description = description
70 new.author = created_by_user
71 new.author = created_by_user
71 self.sa.add(new)
72 self.sa.add(new)
72 Session().flush()
73 Session().flush()
73 #members
74 #members
74 for member in reviewers:
75 for member in reviewers:
75 _usr = self._get_user(member)
76 _usr = self._get_user(member)
76 reviewer = PullRequestReviewers(_usr, new)
77 reviewer = PullRequestReviewers(_usr, new)
77 self.sa.add(reviewer)
78 self.sa.add(reviewer)
78
79
79 #notification to reviewers
80 #notification to reviewers
80 notif = NotificationModel()
81 notif = NotificationModel()
81
82
82 pr_url = h.url('pullrequest_show', repo_name=other_repo.repo_name,
83 pr_url = h.url('pullrequest_show', repo_name=other_repo.repo_name,
83 pull_request_id=new.pull_request_id,
84 pull_request_id=new.pull_request_id,
84 qualified=True,
85 qualified=True,
85 )
86 )
86 subject = safe_unicode(
87 subject = safe_unicode(
87 h.link_to(
88 h.link_to(
88 _('%(user)s wants you to review pull request #%(pr_id)s') % \
89 _('%(user)s wants you to review pull request #%(pr_id)s') % \
89 {'user': created_by_user.username,
90 {'user': created_by_user.username,
90 'pr_id': new.pull_request_id},
91 'pr_id': new.pull_request_id},
91 pr_url
92 pr_url
92 )
93 )
93 )
94 )
94 body = description
95 body = description
95 kwargs = {
96 kwargs = {
96 'pr_title': title,
97 'pr_title': title,
97 'pr_user_created': h.person(created_by_user.email),
98 'pr_user_created': h.person(created_by_user.email),
98 'pr_repo_url': h.url('summary_home', repo_name=other_repo.repo_name,
99 'pr_repo_url': h.url('summary_home', repo_name=other_repo.repo_name,
99 qualified=True,),
100 qualified=True,),
100 'pr_url': pr_url,
101 'pr_url': pr_url,
101 'pr_revisions': revisions
102 'pr_revisions': revisions
102 }
103 }
103 notif.create(created_by=created_by_user, subject=subject, body=body,
104 notif.create(created_by=created_by_user, subject=subject, body=body,
104 recipients=reviewers,
105 recipients=reviewers,
105 type_=Notification.TYPE_PULL_REQUEST, email_kwargs=kwargs)
106 type_=Notification.TYPE_PULL_REQUEST, email_kwargs=kwargs)
106 return new
107 return new
107
108
108 def update_reviewers(self, pull_request, reviewers_ids):
109 def update_reviewers(self, pull_request, reviewers_ids):
109 reviewers_ids = set(reviewers_ids)
110 reviewers_ids = set(reviewers_ids)
110 pull_request = self.__get_pull_request(pull_request)
111 pull_request = self.__get_pull_request(pull_request)
111 current_reviewers = PullRequestReviewers.query()\
112 current_reviewers = PullRequestReviewers.query()\
112 .filter(PullRequestReviewers.pull_request==
113 .filter(PullRequestReviewers.pull_request==
113 pull_request)\
114 pull_request)\
114 .all()
115 .all()
115 current_reviewers_ids = set([x.user.user_id for x in current_reviewers])
116 current_reviewers_ids = set([x.user.user_id for x in current_reviewers])
116
117
117 to_add = reviewers_ids.difference(current_reviewers_ids)
118 to_add = reviewers_ids.difference(current_reviewers_ids)
118 to_remove = current_reviewers_ids.difference(reviewers_ids)
119 to_remove = current_reviewers_ids.difference(reviewers_ids)
119
120
120 log.debug("Adding %s reviewers" % to_add)
121 log.debug("Adding %s reviewers" % to_add)
121 log.debug("Removing %s reviewers" % to_remove)
122 log.debug("Removing %s reviewers" % to_remove)
122
123
123 for uid in to_add:
124 for uid in to_add:
124 _usr = self._get_user(uid)
125 _usr = self._get_user(uid)
125 reviewer = PullRequestReviewers(_usr, pull_request)
126 reviewer = PullRequestReviewers(_usr, pull_request)
126 self.sa.add(reviewer)
127 self.sa.add(reviewer)
127
128
128 for uid in to_remove:
129 for uid in to_remove:
129 reviewer = PullRequestReviewers.query()\
130 reviewer = PullRequestReviewers.query()\
130 .filter(PullRequestReviewers.user_id==uid,
131 .filter(PullRequestReviewers.user_id==uid,
131 PullRequestReviewers.pull_request==pull_request)\
132 PullRequestReviewers.pull_request==pull_request)\
132 .scalar()
133 .scalar()
133 if reviewer:
134 if reviewer:
134 self.sa.delete(reviewer)
135 self.sa.delete(reviewer)
135
136
136 def delete(self, pull_request):
137 def delete(self, pull_request):
137 pull_request = self.__get_pull_request(pull_request)
138 pull_request = self.__get_pull_request(pull_request)
138 Session().delete(pull_request)
139 Session().delete(pull_request)
139
140
140 def close_pull_request(self, pull_request):
141 def close_pull_request(self, pull_request):
141 pull_request = self.__get_pull_request(pull_request)
142 pull_request = self.__get_pull_request(pull_request)
142 pull_request.status = PullRequest.STATUS_CLOSED
143 pull_request.status = PullRequest.STATUS_CLOSED
143 pull_request.updated_on = datetime.datetime.now()
144 pull_request.updated_on = datetime.datetime.now()
144 self.sa.add(pull_request)
145 self.sa.add(pull_request)
145
146
146 def _get_changesets(self, org_repo, org_ref, other_repo, other_ref,
147 def _get_changesets(self, org_repo, org_ref, other_repo, other_ref,
147 discovery_data):
148 discovery_data):
148 """
149 """
149 Returns a list of changesets that are incoming from org_repo@org_ref
150 Returns a list of changesets that are incoming from org_repo@org_ref
150 to other_repo@other_ref
151 to other_repo@other_ref
151
152
152 :param org_repo:
153 :param org_repo:
153 :type org_repo:
154 :type org_repo:
154 :param org_ref:
155 :param org_ref:
155 :type org_ref:
156 :type org_ref:
156 :param other_repo:
157 :param other_repo:
157 :type other_repo:
158 :type other_repo:
158 :param other_ref:
159 :param other_ref:
159 :type other_ref:
160 :type other_ref:
160 :param tmp:
161 :param tmp:
161 :type tmp:
162 :type tmp:
162 """
163 """
163 changesets = []
164 changesets = []
164 #case two independent repos
165 #case two independent repos
165 common, incoming, rheads = discovery_data
166 common, incoming, rheads = discovery_data
166 if org_repo != other_repo and incoming:
167 if org_repo != other_repo and incoming:
167 revs = org_repo._repo.changelog.findmissing(common, rheads)
168 obj = findcommonoutgoing(org_repo._repo,
169 localrepo.locallegacypeer(other_repo._repo.local()))
170 revs = obj.missing
168
171
169 for cs in reversed(map(binascii.hexlify, revs)):
172 for cs in reversed(map(binascii.hexlify, revs)):
170 changesets.append(org_repo.get_changeset(cs))
173 changesets.append(org_repo.get_changeset(cs))
171 else:
174 else:
172 _revset_predicates = {
175 _revset_predicates = {
173 'branch': 'branch',
176 'branch': 'branch',
174 'book': 'bookmark',
177 'book': 'bookmark',
175 'tag': 'tag',
178 'tag': 'tag',
176 'rev': 'id',
179 'rev': 'id',
177 }
180 }
178
181
179 revs = [
182 revs = [
180 "ancestors(%s('%s')) and not ancestors(%s('%s'))" % (
183 "ancestors(%s('%s')) and not ancestors(%s('%s'))" % (
181 _revset_predicates[org_ref[0]], org_ref[1],
184 _revset_predicates[org_ref[0]], org_ref[1],
182 _revset_predicates[other_ref[0]], other_ref[1]
185 _revset_predicates[other_ref[0]], other_ref[1]
183 )
186 )
184 ]
187 ]
185
188
186 out = scmutil.revrange(org_repo._repo, revs)
189 out = scmutil.revrange(org_repo._repo, revs)
187 for cs in reversed(out):
190 for cs in reversed(out):
188 changesets.append(org_repo.get_changeset(cs))
191 changesets.append(org_repo.get_changeset(cs))
189
192
190 return changesets
193 return changesets
191
194
192 def _get_discovery(self, org_repo, org_ref, other_repo, other_ref):
195 def _get_discovery(self, org_repo, org_ref, other_repo, other_ref):
193 """
196 """
194 Get's mercurial discovery data used to calculate difference between
197 Get's mercurial discovery data used to calculate difference between
195 repos and refs
198 repos and refs
196
199
197 :param org_repo:
200 :param org_repo:
198 :type org_repo:
201 :type org_repo:
199 :param org_ref:
202 :param org_ref:
200 :type org_ref:
203 :type org_ref:
201 :param other_repo:
204 :param other_repo:
202 :type other_repo:
205 :type other_repo:
203 :param other_ref:
206 :param other_ref:
204 :type other_ref:
207 :type other_ref:
205 """
208 """
206
209
207 _org_repo = org_repo._repo
210 _org_repo = org_repo._repo
208 org_rev_type, org_rev = org_ref
211 org_rev_type, org_rev = org_ref
209
212
210 _other_repo = other_repo._repo
213 _other_repo = other_repo._repo
211 other_rev_type, other_rev = other_ref
214 other_rev_type, other_rev = other_ref
212
215
213 log.debug('Doing discovery for %s@%s vs %s@%s' % (
216 log.debug('Doing discovery for %s@%s vs %s@%s' % (
214 org_repo, org_ref, other_repo, other_ref)
217 org_repo, org_ref, other_repo, other_ref)
215 )
218 )
216 #log.debug('Filter heads are %s[%s]' % ('', org_ref[1]))
219 #log.debug('Filter heads are %s[%s]' % ('', org_ref[1]))
217 org_peer = localrepo.locallegacypeer(_org_repo.local())
220 org_peer = localrepo.locallegacypeer(_org_repo.local())
218 tmp = discovery.findcommonincoming(
221 tmp = discovery.findcommonincoming(
219 repo=_other_repo, # other_repo we check for incoming
222 repo=_other_repo, # other_repo we check for incoming
220 remote=org_peer, # org_repo source for incoming
223 remote=org_peer, # org_repo source for incoming
221 heads=[_other_repo[other_rev].node(),
224 heads=[_other_repo[other_rev].node(),
222 _org_repo[org_rev].node()],
225 _org_repo[org_rev].node()],
223 force=False
226 force=False
224 )
227 )
225 return tmp
228 return tmp
226
229
227 def get_compare_data(self, org_repo, org_ref, other_repo, other_ref):
230 def get_compare_data(self, org_repo, org_ref, other_repo, other_ref):
228 """
231 """
229 Returns a tuple of incomming changesets, and discoverydata cache
232 Returns a tuple of incomming changesets, and discoverydata cache
230
233
231 :param org_repo:
234 :param org_repo:
232 :type org_repo:
235 :type org_repo:
233 :param org_ref:
236 :param org_ref:
234 :type org_ref:
237 :type org_ref:
235 :param other_repo:
238 :param other_repo:
236 :type other_repo:
239 :type other_repo:
237 :param other_ref:
240 :param other_ref:
238 :type other_ref:
241 :type other_ref:
239 """
242 """
240
243
241 if len(org_ref) != 2 or not isinstance(org_ref, (list, tuple)):
244 if len(org_ref) != 2 or not isinstance(org_ref, (list, tuple)):
242 raise Exception('org_ref must be a two element list/tuple')
245 raise Exception('org_ref must be a two element list/tuple')
243
246
244 if len(other_ref) != 2 or not isinstance(org_ref, (list, tuple)):
247 if len(other_ref) != 2 or not isinstance(org_ref, (list, tuple)):
245 raise Exception('other_ref must be a two element list/tuple')
248 raise Exception('other_ref must be a two element list/tuple')
246
249
247 discovery_data = self._get_discovery(org_repo.scm_instance,
250 discovery_data = self._get_discovery(org_repo.scm_instance,
248 org_ref,
251 org_ref,
249 other_repo.scm_instance,
252 other_repo.scm_instance,
250 other_ref)
253 other_ref)
251 cs_ranges = self._get_changesets(org_repo.scm_instance,
254 cs_ranges = self._get_changesets(org_repo.scm_instance,
252 org_ref,
255 org_ref,
253 other_repo.scm_instance,
256 other_repo.scm_instance,
254 other_ref,
257 other_ref,
255 discovery_data)
258 discovery_data)
256
259
257 return cs_ranges, discovery_data
260 return cs_ranges, discovery_data
@@ -1,189 +1,293 b''
1 from rhodecode.tests import *
1 from rhodecode.tests import *
2 from rhodecode.model.repo import RepoModel
2 from rhodecode.model.repo import RepoModel
3 from rhodecode.model.meta import Session
3 from rhodecode.model.meta import Session
4 from rhodecode.model.db import Repository
4 from rhodecode.model.db import Repository
5 from rhodecode.model.scm import ScmModel
5 from rhodecode.model.scm import ScmModel
6 from rhodecode.lib.vcs.backends.base import EmptyChangeset
6 from rhodecode.lib.vcs.backends.base import EmptyChangeset
7
7
8
8
9 class TestCompareController(TestController):
9 class TestCompareController(TestController):
10
10
11 def test_index_tag(self):
11 def test_index_tag(self):
12 self.log_user()
12 self.log_user()
13 tag1 = '0.1.3'
13 tag1 = '0.1.3'
14 tag2 = '0.1.2'
14 tag2 = '0.1.2'
15 response = self.app.get(url(controller='compare', action='index',
15 response = self.app.get(url(controller='compare', action='index',
16 repo_name=HG_REPO,
16 repo_name=HG_REPO,
17 org_ref_type="tag",
17 org_ref_type="tag",
18 org_ref=tag1,
18 org_ref=tag1,
19 other_ref_type="tag",
19 other_ref_type="tag",
20 other_ref=tag2,
20 other_ref=tag2,
21 ))
21 ))
22 response.mustcontain('%s@%s -> %s@%s' % (HG_REPO, tag1, HG_REPO, tag2))
22 response.mustcontain('%s@%s -> %s@%s' % (HG_REPO, tag1, HG_REPO, tag2))
23 ## outgoing changesets between tags
23 ## outgoing changesets between tags
24 response.mustcontain('''<a href="/%s/changeset/17544fbfcd33ffb439e2b728b5d526b1ef30bfcf">r120:17544fbfcd33</a>''' % HG_REPO)
24 response.mustcontain('''<a href="/%s/changeset/17544fbfcd33ffb439e2b728b5d526b1ef30bfcf">r120:17544fbfcd33</a>''' % HG_REPO)
25 response.mustcontain('''<a href="/%s/changeset/36e0fc9d2808c5022a24f49d6658330383ed8666">r119:36e0fc9d2808</a>''' % HG_REPO)
25 response.mustcontain('''<a href="/%s/changeset/36e0fc9d2808c5022a24f49d6658330383ed8666">r119:36e0fc9d2808</a>''' % HG_REPO)
26 response.mustcontain('''<a href="/%s/changeset/bb1a3ab98cc45cb934a77dcabf87a5a598b59e97">r118:bb1a3ab98cc4</a>''' % HG_REPO)
26 response.mustcontain('''<a href="/%s/changeset/bb1a3ab98cc45cb934a77dcabf87a5a598b59e97">r118:bb1a3ab98cc4</a>''' % HG_REPO)
27 response.mustcontain('''<a href="/%s/changeset/41fda979f02fda216374bf8edac4e83f69e7581c">r117:41fda979f02f</a>''' % HG_REPO)
27 response.mustcontain('''<a href="/%s/changeset/41fda979f02fda216374bf8edac4e83f69e7581c">r117:41fda979f02f</a>''' % HG_REPO)
28 response.mustcontain('''<a href="/%s/changeset/9749bfbfc0d2eba208d7947de266303b67c87cda">r116:9749bfbfc0d2</a>''' % HG_REPO)
28 response.mustcontain('''<a href="/%s/changeset/9749bfbfc0d2eba208d7947de266303b67c87cda">r116:9749bfbfc0d2</a>''' % HG_REPO)
29 response.mustcontain('''<a href="/%s/changeset/70d4cef8a37657ee4cf5aabb3bd9f68879769816">r115:70d4cef8a376</a>''' % HG_REPO)
29 response.mustcontain('''<a href="/%s/changeset/70d4cef8a37657ee4cf5aabb3bd9f68879769816">r115:70d4cef8a376</a>''' % HG_REPO)
30 response.mustcontain('''<a href="/%s/changeset/c5ddebc06eaaba3010c2d66ea6ec9d074eb0f678">r112:c5ddebc06eaa</a>''' % HG_REPO)
30 response.mustcontain('''<a href="/%s/changeset/c5ddebc06eaaba3010c2d66ea6ec9d074eb0f678">r112:c5ddebc06eaa</a>''' % HG_REPO)
31
31
32 ## files diff
32 ## files diff
33 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--1c5cf9e91c12">docs/api/utils/index.rst</a></div>''' % (HG_REPO, tag1, tag2))
33 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--1c5cf9e91c12">docs/api/utils/index.rst</a></div>''' % (HG_REPO, tag1, tag2))
34 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--e3305437df55">test_and_report.sh</a></div>''' % (HG_REPO, tag1, tag2))
34 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--e3305437df55">test_and_report.sh</a></div>''' % (HG_REPO, tag1, tag2))
35 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--c8e92ef85cd1">.hgignore</a></div>''' % (HG_REPO, tag1, tag2))
35 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--c8e92ef85cd1">.hgignore</a></div>''' % (HG_REPO, tag1, tag2))
36 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--6e08b694d687">.hgtags</a></div>''' % (HG_REPO, tag1, tag2))
36 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--6e08b694d687">.hgtags</a></div>''' % (HG_REPO, tag1, tag2))
37 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--2c14b00f3393">docs/api/index.rst</a></div>''' % (HG_REPO, tag1, tag2))
37 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--2c14b00f3393">docs/api/index.rst</a></div>''' % (HG_REPO, tag1, tag2))
38 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--430ccbc82bdf">vcs/__init__.py</a></div>''' % (HG_REPO, tag1, tag2))
38 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--430ccbc82bdf">vcs/__init__.py</a></div>''' % (HG_REPO, tag1, tag2))
39 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--9c390eb52cd6">vcs/backends/hg.py</a></div>''' % (HG_REPO, tag1, tag2))
39 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--9c390eb52cd6">vcs/backends/hg.py</a></div>''' % (HG_REPO, tag1, tag2))
40 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--ebb592c595c0">vcs/utils/__init__.py</a></div>''' % (HG_REPO, tag1, tag2))
40 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--ebb592c595c0">vcs/utils/__init__.py</a></div>''' % (HG_REPO, tag1, tag2))
41 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--7abc741b5052">vcs/utils/annotate.py</a></div>''' % (HG_REPO, tag1, tag2))
41 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--7abc741b5052">vcs/utils/annotate.py</a></div>''' % (HG_REPO, tag1, tag2))
42 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--2ef0ef106c56">vcs/utils/diffs.py</a></div>''' % (HG_REPO, tag1, tag2))
42 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--2ef0ef106c56">vcs/utils/diffs.py</a></div>''' % (HG_REPO, tag1, tag2))
43 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--3150cb87d4b7">vcs/utils/lazy.py</a></div>''' % (HG_REPO, tag1, tag2))
43 response.mustcontain('''<div class="node"><a href="/%s/compare/tag@%s...tag@%s#C--3150cb87d4b7">vcs/utils/lazy.py</a></div>''' % (HG_REPO, tag1, tag2))
44
44
45 def test_index_branch(self):
45 def test_index_branch(self):
46 self.log_user()
46 self.log_user()
47 response = self.app.get(url(controller='compare', action='index',
47 response = self.app.get(url(controller='compare', action='index',
48 repo_name=HG_REPO,
48 repo_name=HG_REPO,
49 org_ref_type="branch",
49 org_ref_type="branch",
50 org_ref='default',
50 org_ref='default',
51 other_ref_type="branch",
51 other_ref_type="branch",
52 other_ref='default',
52 other_ref='default',
53 ))
53 ))
54
54
55 response.mustcontain('%s@default -> %s@default' % (HG_REPO, HG_REPO))
55 response.mustcontain('%s@default -> %s@default' % (HG_REPO, HG_REPO))
56 # branch are equal
56 # branch are equal
57 response.mustcontain('<tr><td>No changesets</td></tr>')
57 response.mustcontain('<tr><td>No changesets</td></tr>')
58
58
59 def test_compare_revisions(self):
59 def test_compare_revisions(self):
60 self.log_user()
60 self.log_user()
61 rev1 = '3d8f361e72ab'
61 rev1 = '3d8f361e72ab'
62 rev2 = 'b986218ba1c9'
62 rev2 = 'b986218ba1c9'
63 response = self.app.get(url(controller='compare', action='index',
63 response = self.app.get(url(controller='compare', action='index',
64 repo_name=HG_REPO,
64 repo_name=HG_REPO,
65 org_ref_type="rev",
65 org_ref_type="rev",
66 org_ref=rev1,
66 org_ref=rev1,
67 other_ref_type="rev",
67 other_ref_type="rev",
68 other_ref=rev2,
68 other_ref=rev2,
69 ))
69 ))
70 response.mustcontain('%s@%s -> %s@%s' % (HG_REPO, rev1, HG_REPO, rev2))
70 response.mustcontain('%s@%s -> %s@%s' % (HG_REPO, rev1, HG_REPO, rev2))
71 ## outgoing changesets between those revisions
71 ## outgoing changesets between those revisions
72 response.mustcontain("""<a href="/%s/changeset/3d8f361e72ab303da48d799ff1ac40d5ac37c67e">r1:%s</a>""" % (HG_REPO, rev1))
72 response.mustcontain("""<a href="/%s/changeset/3d8f361e72ab303da48d799ff1ac40d5ac37c67e">r1:%s</a>""" % (HG_REPO, rev1))
73
73
74 ## files
74 ## files
75 response.mustcontain("""<a href="/%s/compare/rev@%s...rev@%s#C--c8e92ef85cd1">.hgignore</a>""" % (HG_REPO, rev1, rev2))
75 response.mustcontain("""<a href="/%s/compare/rev@%s...rev@%s#C--c8e92ef85cd1">.hgignore</a>""" % (HG_REPO, rev1, rev2))
76
76
77 def test_compare_remote_repos(self):
77 def test_compare_remote_repos(self):
78 self.log_user()
78 self.log_user()
79
79
80 form_data = dict(
80 form_data = dict(
81 repo_name=HG_FORK,
81 repo_name=HG_FORK,
82 repo_name_full=HG_FORK,
82 repo_name_full=HG_FORK,
83 repo_group=None,
83 repo_group=None,
84 repo_type='hg',
84 repo_type='hg',
85 description='',
85 description='',
86 private=False,
86 private=False,
87 copy_permissions=False,
87 copy_permissions=False,
88 landing_rev='tip',
88 landing_rev='tip',
89 update_after_clone=False,
89 update_after_clone=False,
90 fork_parent_id=Repository.get_by_repo_name(HG_REPO),
90 fork_parent_id=Repository.get_by_repo_name(HG_REPO),
91 )
91 )
92 RepoModel().create_fork(form_data, cur_user=TEST_USER_ADMIN_LOGIN)
92 RepoModel().create_fork(form_data, cur_user=TEST_USER_ADMIN_LOGIN)
93
93
94 Session().commit()
94 Session().commit()
95
95
96 rev1 = '7d4bc8ec6be5'
96 rev1 = '7d4bc8ec6be5'
97 rev2 = '56349e29c2af'
97 rev2 = '56349e29c2af'
98
98
99 response = self.app.get(url(controller='compare', action='index',
99 response = self.app.get(url(controller='compare', action='index',
100 repo_name=HG_REPO,
100 repo_name=HG_REPO,
101 org_ref_type="rev",
101 org_ref_type="rev",
102 org_ref=rev1,
102 org_ref=rev1,
103 other_ref_type="rev",
103 other_ref_type="rev",
104 other_ref=rev2,
104 other_ref=rev2,
105 repo=HG_FORK
105 repo=HG_FORK
106 ))
106 ))
107
107
108 try:
108 try:
109 response.mustcontain('%s@%s -> %s@%s' % (HG_REPO, rev1, HG_FORK, rev2))
109 response.mustcontain('%s@%s -> %s@%s' % (HG_REPO, rev1, HG_FORK, rev2))
110 ## outgoing changesets between those revisions
110 ## outgoing changesets between those revisions
111
111
112 response.mustcontain("""<a href="/%s/changeset/7d4bc8ec6be56c0f10425afb40b6fc315a4c25e7">r6:%s</a>""" % (HG_REPO, rev1))
112 response.mustcontain("""<a href="/%s/changeset/7d4bc8ec6be56c0f10425afb40b6fc315a4c25e7">r6:%s</a>""" % (HG_REPO, rev1))
113 response.mustcontain("""<a href="/%s/changeset/6fff84722075f1607a30f436523403845f84cd9e">r5:6fff84722075</a>""" % (HG_REPO))
113 response.mustcontain("""<a href="/%s/changeset/6fff84722075f1607a30f436523403845f84cd9e">r5:6fff84722075</a>""" % (HG_REPO))
114 response.mustcontain("""<a href="/%s/changeset/2dda4e345facb0ccff1a191052dd1606dba6781d">r4:2dda4e345fac</a>""" % (HG_REPO))
114 response.mustcontain("""<a href="/%s/changeset/2dda4e345facb0ccff1a191052dd1606dba6781d">r4:2dda4e345fac</a>""" % (HG_REPO))
115
115
116 ## files
116 ## files
117 response.mustcontain("""<a href="/%s/compare/rev@%s...rev@%s#C--9c390eb52cd6">vcs/backends/hg.py</a>""" % (HG_REPO, rev1, rev2))
117 response.mustcontain("""<a href="/%s/compare/rev@%s...rev@%s#C--9c390eb52cd6">vcs/backends/hg.py</a>""" % (HG_REPO, rev1, rev2))
118 response.mustcontain("""<a href="/%s/compare/rev@%s...rev@%s#C--41b41c1f2796">vcs/backends/__init__.py</a>""" % (HG_REPO, rev1, rev2))
118 response.mustcontain("""<a href="/%s/compare/rev@%s...rev@%s#C--41b41c1f2796">vcs/backends/__init__.py</a>""" % (HG_REPO, rev1, rev2))
119 response.mustcontain("""<a href="/%s/compare/rev@%s...rev@%s#C--2f574d260608">vcs/backends/base.py</a>""" % (HG_REPO, rev1, rev2))
119 response.mustcontain("""<a href="/%s/compare/rev@%s...rev@%s#C--2f574d260608">vcs/backends/base.py</a>""" % (HG_REPO, rev1, rev2))
120 finally:
120 finally:
121 RepoModel().delete(HG_FORK)
121 RepoModel().delete(HG_FORK)
122
122
123 def test_compare_extra_commits(self):
123 def test_compare_extra_commits(self):
124 self.log_user()
124 self.log_user()
125
125
126 repo1 = RepoModel().create_repo(repo_name='one', repo_type='hg',
126 repo1 = RepoModel().create_repo(repo_name='one', repo_type='hg',
127 description='diff-test',
127 description='diff-test',
128 owner=TEST_USER_ADMIN_LOGIN)
128 owner=TEST_USER_ADMIN_LOGIN)
129
129
130 repo2 = RepoModel().create_repo(repo_name='one-fork', repo_type='hg',
130 repo2 = RepoModel().create_repo(repo_name='one-fork', repo_type='hg',
131 description='diff-test',
131 description='diff-test',
132 owner=TEST_USER_ADMIN_LOGIN)
132 owner=TEST_USER_ADMIN_LOGIN)
133
133
134 Session().commit()
134 Session().commit()
135 r1_id = repo1.repo_id
135 r1_id = repo1.repo_id
136 r1_name = repo1.repo_name
136 r1_name = repo1.repo_name
137 r2_id = repo2.repo_id
137 r2_id = repo2.repo_id
138 r2_name = repo2.repo_name
138 r2_name = repo2.repo_name
139
139
140 #commit something !
140 #commit something !
141 cs0 = ScmModel().create_node(
141 cs0 = ScmModel().create_node(
142 repo=repo1.scm_instance, repo_name=r1_name,
142 repo=repo1.scm_instance, repo_name=r1_name,
143 cs=EmptyChangeset(alias='hg'), user=TEST_USER_ADMIN_LOGIN,
143 cs=EmptyChangeset(alias='hg'), user=TEST_USER_ADMIN_LOGIN,
144 author=TEST_USER_ADMIN_LOGIN,
144 author=TEST_USER_ADMIN_LOGIN,
145 message='commit1',
145 message='commit1',
146 content='line1',
146 content='line1',
147 f_path='file1'
147 f_path='file1'
148 )
148 )
149
149
150 cs0_prim = ScmModel().create_node(
150 cs0_prim = ScmModel().create_node(
151 repo=repo2.scm_instance, repo_name=r2_name,
151 repo=repo2.scm_instance, repo_name=r2_name,
152 cs=EmptyChangeset(alias='hg'), user=TEST_USER_ADMIN_LOGIN,
152 cs=EmptyChangeset(alias='hg'), user=TEST_USER_ADMIN_LOGIN,
153 author=TEST_USER_ADMIN_LOGIN,
153 author=TEST_USER_ADMIN_LOGIN,
154 message='commit1',
154 message='commit1',
155 content='line1',
155 content='line1',
156 f_path='file1'
156 f_path='file1'
157 )
157 )
158
158
159 cs1 = ScmModel().commit_change(
159 cs1 = ScmModel().commit_change(
160 repo=repo2.scm_instance, repo_name=r2_name,
160 repo=repo2.scm_instance, repo_name=r2_name,
161 cs=cs0_prim, user=TEST_USER_ADMIN_LOGIN, author=TEST_USER_ADMIN_LOGIN,
161 cs=cs0_prim, user=TEST_USER_ADMIN_LOGIN, author=TEST_USER_ADMIN_LOGIN,
162 message='commit2',
162 message='commit2',
163 content='line1\nline2',
163 content='line1\nline2',
164 f_path='file1'
164 f_path='file1'
165 )
165 )
166
166
167 rev1 = 'default'
167 rev1 = 'default'
168 rev2 = 'default'
168 rev2 = 'default'
169 response = self.app.get(url(controller='compare', action='index',
169 response = self.app.get(url(controller='compare', action='index',
170 repo_name=r2_name,
170 repo_name=r2_name,
171 org_ref_type="branch",
171 org_ref_type="branch",
172 org_ref=rev1,
172 org_ref=rev1,
173 other_ref_type="branch",
173 other_ref_type="branch",
174 other_ref=rev2,
174 other_ref=rev2,
175 repo=r1_name
175 repo=r1_name
176 ))
176 ))
177
177
178 try:
178 try:
179 response.mustcontain('%s@%s -> %s@%s' % (r2_name, rev1, r1_name, rev2))
179 response.mustcontain('%s@%s -> %s@%s' % (r2_name, rev1, r1_name, rev2))
180
180
181 response.mustcontain("""<div class="message">commit2</div>""")
181 response.mustcontain("""<div class="message">commit2</div>""")
182 response.mustcontain("""<a href="/%s/changeset/%s">r1:%s</a>""" % (r2_name, cs1.raw_id, cs1.short_id))
182 response.mustcontain("""<a href="/%s/changeset/%s">r1:%s</a>""" % (r2_name, cs1.raw_id, cs1.short_id))
183 ## files
183 ## files
184 response.mustcontain("""<a href="/%s/compare/branch@%s...branch@%s#C--826e8142e6ba">file1</a>""" % (r2_name, rev1, rev2))
184 response.mustcontain("""<a href="/%s/compare/branch@%s...branch@%s#C--826e8142e6ba">file1</a>""" % (r2_name, rev1, rev2))
185
185
186
187 finally:
186 finally:
188 RepoModel().delete(r1_id)
187 RepoModel().delete(r1_id)
189 RepoModel().delete(r2_id)
188 RepoModel().delete(r2_id)
189
190 def test_org_repo_new_commits_after_forking(self):
191 self.log_user()
192
193 repo1 = RepoModel().create_repo(repo_name='one', repo_type='hg',
194 description='diff-test',
195 owner=TEST_USER_ADMIN_LOGIN)
196
197 Session().commit()
198 r1_id = repo1.repo_id
199 r1_name = repo1.repo_name
200
201 #commit something initially !
202 cs0 = ScmModel().create_node(
203 repo=repo1.scm_instance, repo_name=r1_name,
204 cs=EmptyChangeset(alias='hg'), user=TEST_USER_ADMIN_LOGIN,
205 author=TEST_USER_ADMIN_LOGIN,
206 message='commit1',
207 content='line1',
208 f_path='file1'
209 )
210 Session().commit()
211 self.assertEqual(repo1.scm_instance.revisions, [cs0.raw_id])
212 #fork the repo1
213 repo2 = RepoModel().create_repo(repo_name='one-fork', repo_type='hg',
214 description='compare-test',
215 clone_uri=repo1.repo_full_path,
216 owner=TEST_USER_ADMIN_LOGIN, fork_of='one')
217 Session().commit()
218 self.assertEqual(repo2.scm_instance.revisions, [cs0.raw_id])
219 r2_id = repo2.repo_id
220 r2_name = repo2.repo_name
221
222 #make 3 new commits in fork
223 cs1 = ScmModel().create_node(
224 repo=repo2.scm_instance, repo_name=r2_name,
225 cs=repo2.scm_instance[-1], user=TEST_USER_ADMIN_LOGIN,
226 author=TEST_USER_ADMIN_LOGIN,
227 message='commit1-fork',
228 content='file1-line1-from-fork',
229 f_path='file1-fork'
230 )
231 cs2 = ScmModel().create_node(
232 repo=repo2.scm_instance, repo_name=r2_name,
233 cs=cs1, user=TEST_USER_ADMIN_LOGIN,
234 author=TEST_USER_ADMIN_LOGIN,
235 message='commit2-fork',
236 content='file2-line1-from-fork',
237 f_path='file2-fork'
238 )
239 cs3 = ScmModel().create_node(
240 repo=repo2.scm_instance, repo_name=r2_name,
241 cs=cs2, user=TEST_USER_ADMIN_LOGIN,
242 author=TEST_USER_ADMIN_LOGIN,
243 message='commit3-fork',
244 content='file3-line1-from-fork',
245 f_path='file3-fork'
246 )
247
248 #compare !
249 rev1 = 'default'
250 rev2 = 'default'
251 response = self.app.get(url(controller='compare', action='index',
252 repo_name=r2_name,
253 org_ref_type="branch",
254 org_ref=rev1,
255 other_ref_type="branch",
256 other_ref=rev2,
257 repo=r1_name
258 ))
259
260 try:
261 response.mustcontain('%s@%s -> %s@%s' % (r2_name, rev1, r1_name, rev2))
262 response.mustcontain("""file1-line1-from-fork""")
263 response.mustcontain("""file2-line1-from-fork""")
264 response.mustcontain("""file3-line1-from-fork""")
265
266 #add new commit into parent !
267 cs0 = ScmModel().create_node(
268 repo=repo1.scm_instance, repo_name=r1_name,
269 cs=EmptyChangeset(alias='hg'), user=TEST_USER_ADMIN_LOGIN,
270 author=TEST_USER_ADMIN_LOGIN,
271 message='commit2',
272 content='line1',
273 f_path='file2'
274 )
275 #compare !
276 rev1 = 'default'
277 rev2 = 'default'
278 response = self.app.get(url(controller='compare', action='index',
279 repo_name=r2_name,
280 org_ref_type="branch",
281 org_ref=rev1,
282 other_ref_type="branch",
283 other_ref=rev2,
284 repo=r1_name
285 ))
286
287 response.mustcontain('%s@%s -> %s@%s' % (r2_name, rev1, r1_name, rev2))
288 response.mustcontain("""file1-line1-from-fork""")
289 response.mustcontain("""file2-line1-from-fork""")
290 response.mustcontain("""file3-line1-from-fork""")
291 finally:
292 RepoModel().delete(r1_id)
293 RepoModel().delete(r2_id)
General Comments 0
You need to be logged in to leave comments. Login now