##// END OF EJS Templates
changes for #56
marcink -
r1171:2ab211e0 beta
parent child Browse files
Show More
@@ -0,0 +1,52 b''
1 import logging
2
3 from pylons import request, response, session, tmpl_context as c, url
4 from pylons.controllers.util import abort, redirect
5
6 from rhodecode.lib.base import BaseController, render
7
8 log = logging.getLogger(__name__)
9
10 class ReposGroupsController(BaseController):
11 """REST Controller styled on the Atom Publishing Protocol"""
12 # To properly map this controller, ensure your config/routing.py
13 # file has a resource setup:
14 # map.resource('repos_group', 'repos_groups')
15
16 def index(self, format='html'):
17 """GET /repos_groups: All items in the collection"""
18 # url('repos_groups')
19
20 def create(self):
21 """POST /repos_groups: Create a new item"""
22 # url('repos_groups')
23
24 def new(self, format='html'):
25 """GET /repos_groups/new: Form to create a new item"""
26 # url('new_repos_group')
27
28 def update(self, id):
29 """PUT /repos_groups/id: Update an existing item"""
30 # Forms posted to this method should contain a hidden field:
31 # <input type="hidden" name="_method" value="PUT" />
32 # Or using helpers:
33 # h.form(url('repos_group', id=ID),
34 # method='put')
35 # url('repos_group', id=ID)
36
37 def delete(self, id):
38 """DELETE /repos_groups/id: Delete an existing item"""
39 # Forms posted to this method should contain a hidden field:
40 # <input type="hidden" name="_method" value="DELETE" />
41 # Or using helpers:
42 # h.form(url('repos_group', id=ID),
43 # method='delete')
44 # url('repos_group', id=ID)
45
46 def show(self, id, format='html'):
47 """GET /repos_groups/id: Show a specific item"""
48 # url('repos_group', id=ID)
49
50 def edit(self, id, format='html'):
51 """GET /repos_groups/id/edit: Form to edit an existing item"""
52 # url('edit_repos_group', id=ID)
@@ -0,0 +1,43 b''
1 from rhodecode.tests import *
2
3 class TestReposGroupsController(TestController):
4
5 def test_index(self):
6 response = self.app.get(url('repos_groups'))
7 # Test response...
8
9 def test_index_as_xml(self):
10 response = self.app.get(url('formatted_repos_groups', format='xml'))
11
12 def test_create(self):
13 response = self.app.post(url('repos_groups'))
14
15 def test_new(self):
16 response = self.app.get(url('new_repos_group'))
17
18 def test_new_as_xml(self):
19 response = self.app.get(url('formatted_new_repos_group', format='xml'))
20
21 def test_update(self):
22 response = self.app.put(url('repos_group', id=1))
23
24 def test_update_browser_fakeout(self):
25 response = self.app.post(url('repos_group', id=1), params=dict(_method='put'))
26
27 def test_delete(self):
28 response = self.app.delete(url('repos_group', id=1))
29
30 def test_delete_browser_fakeout(self):
31 response = self.app.post(url('repos_group', id=1), params=dict(_method='delete'))
32
33 def test_show(self):
34 response = self.app.get(url('repos_group', id=1))
35
36 def test_show_as_xml(self):
37 response = self.app.get(url('formatted_repos_group', id=1, format='xml'))
38
39 def test_edit(self):
40 response = self.app.get(url('edit_repos_group', id=1))
41
42 def test_edit_as_xml(self):
43 response = self.app.get(url('formatted_edit_repos_group', id=1, format='xml'))
@@ -1,239 +1,241 b''
1 """
1 """
2 Routes configuration
2 Routes configuration
3
3
4 The more specific and detailed routes should be defined first so they
4 The more specific and detailed routes should be defined first so they
5 may take precedent over the more generic routes. For more information
5 may take precedent over the more generic routes. For more information
6 refer to the routes manual at http://routes.groovie.org/docs/
6 refer to the routes manual at http://routes.groovie.org/docs/
7 """
7 """
8 from __future__ import with_statement
8 from __future__ import with_statement
9 from routes import Mapper
9 from routes import Mapper
10 from rhodecode.lib.utils import check_repo_fast as cr
10 from rhodecode.lib.utils import check_repo_fast as cr
11
11
12 def make_map(config):
12 def make_map(config):
13 """Create, configure and return the routes Mapper"""
13 """Create, configure and return the routes Mapper"""
14 routes_map = Mapper(directory=config['pylons.paths']['controllers'],
14 routes_map = Mapper(directory=config['pylons.paths']['controllers'],
15 always_scan=config['debug'])
15 always_scan=config['debug'])
16 routes_map.minimization = False
16 routes_map.minimization = False
17 routes_map.explicit = False
17 routes_map.explicit = False
18
18
19 def check_repo(environ, match_dict):
19 def check_repo(environ, match_dict):
20 """
20 """
21 check for valid repository for proper 404 handling
21 check for valid repository for proper 404 handling
22
22
23 :param environ:
23 :param environ:
24 :param match_dict:
24 :param match_dict:
25 """
25 """
26 repo_name = match_dict.get('repo_name')
26 repo_name = match_dict.get('repo_name')
27 return not cr(repo_name, config['base_path'])
27 return not cr(repo_name, config['base_path'])
28
28
29 # The ErrorController route (handles 404/500 error pages); it should
29 # The ErrorController route (handles 404/500 error pages); it should
30 # likely stay at the top, ensuring it can always be resolved
30 # likely stay at the top, ensuring it can always be resolved
31 routes_map.connect('/error/{action}', controller='error')
31 routes_map.connect('/error/{action}', controller='error')
32 routes_map.connect('/error/{action}/{id}', controller='error')
32 routes_map.connect('/error/{action}/{id}', controller='error')
33
33
34 #==========================================================================
34 #==========================================================================
35 # CUSTOM ROUTES HERE
35 # CUSTOM ROUTES HERE
36 #==========================================================================
36 #==========================================================================
37
37
38 #MAIN PAGE
38 #MAIN PAGE
39 routes_map.connect('home', '/', controller='home', action='index')
39 routes_map.connect('home', '/', controller='home', action='index')
40 routes_map.connect('repo_switcher', '/repos', controller='home', action='repo_switcher')
40 routes_map.connect('repo_switcher', '/repos', controller='home', action='repo_switcher')
41 routes_map.connect('bugtracker', "http://bitbucket.org/marcinkuzminski/rhodecode/issues", _static=True)
41 routes_map.connect('bugtracker', "http://bitbucket.org/marcinkuzminski/rhodecode/issues", _static=True)
42 routes_map.connect('gpl_license', "http://www.gnu.org/licenses/gpl.html", _static=True)
42 routes_map.connect('gpl_license', "http://www.gnu.org/licenses/gpl.html", _static=True)
43 routes_map.connect('rhodecode_official', "http://rhodecode.org", _static=True)
43 routes_map.connect('rhodecode_official', "http://rhodecode.org", _static=True)
44
44
45 #ADMIN REPOSITORY REST ROUTES
45 #ADMIN REPOSITORY REST ROUTES
46 with routes_map.submapper(path_prefix='/_admin', controller='admin/repos') as m:
46 with routes_map.submapper(path_prefix='/_admin', controller='admin/repos') as m:
47 m.connect("repos", "/repos",
47 m.connect("repos", "/repos",
48 action="create", conditions=dict(method=["POST"]))
48 action="create", conditions=dict(method=["POST"]))
49 m.connect("repos", "/repos",
49 m.connect("repos", "/repos",
50 action="index", conditions=dict(method=["GET"]))
50 action="index", conditions=dict(method=["GET"]))
51 m.connect("formatted_repos", "/repos.{format}",
51 m.connect("formatted_repos", "/repos.{format}",
52 action="index",
52 action="index",
53 conditions=dict(method=["GET"]))
53 conditions=dict(method=["GET"]))
54 m.connect("new_repo", "/repos/new",
54 m.connect("new_repo", "/repos/new",
55 action="new", conditions=dict(method=["GET"]))
55 action="new", conditions=dict(method=["GET"]))
56 m.connect("formatted_new_repo", "/repos/new.{format}",
56 m.connect("formatted_new_repo", "/repos/new.{format}",
57 action="new", conditions=dict(method=["GET"]))
57 action="new", conditions=dict(method=["GET"]))
58 m.connect("/repos/{repo_name:.*}",
58 m.connect("/repos/{repo_name:.*}",
59 action="update", conditions=dict(method=["PUT"],
59 action="update", conditions=dict(method=["PUT"],
60 function=check_repo))
60 function=check_repo))
61 m.connect("/repos/{repo_name:.*}",
61 m.connect("/repos/{repo_name:.*}",
62 action="delete", conditions=dict(method=["DELETE"],
62 action="delete", conditions=dict(method=["DELETE"],
63 function=check_repo))
63 function=check_repo))
64 m.connect("edit_repo", "/repos/{repo_name:.*}/edit",
64 m.connect("edit_repo", "/repos/{repo_name:.*}/edit",
65 action="edit", conditions=dict(method=["GET"],
65 action="edit", conditions=dict(method=["GET"],
66 function=check_repo))
66 function=check_repo))
67 m.connect("formatted_edit_repo", "/repos/{repo_name:.*}.{format}/edit",
67 m.connect("formatted_edit_repo", "/repos/{repo_name:.*}.{format}/edit",
68 action="edit", conditions=dict(method=["GET"],
68 action="edit", conditions=dict(method=["GET"],
69 function=check_repo))
69 function=check_repo))
70 m.connect("repo", "/repos/{repo_name:.*}",
70 m.connect("repo", "/repos/{repo_name:.*}",
71 action="show", conditions=dict(method=["GET"],
71 action="show", conditions=dict(method=["GET"],
72 function=check_repo))
72 function=check_repo))
73 m.connect("formatted_repo", "/repos/{repo_name:.*}.{format}",
73 m.connect("formatted_repo", "/repos/{repo_name:.*}.{format}",
74 action="show", conditions=dict(method=["GET"],
74 action="show", conditions=dict(method=["GET"],
75 function=check_repo))
75 function=check_repo))
76 #ajax delete repo perm user
76 #ajax delete repo perm user
77 m.connect('delete_repo_user', "/repos_delete_user/{repo_name:.*}",
77 m.connect('delete_repo_user', "/repos_delete_user/{repo_name:.*}",
78 action="delete_perm_user", conditions=dict(method=["DELETE"],
78 action="delete_perm_user", conditions=dict(method=["DELETE"],
79 function=check_repo))
79 function=check_repo))
80 #ajax delete repo perm users_group
80 #ajax delete repo perm users_group
81 m.connect('delete_repo_users_group', "/repos_delete_users_group/{repo_name:.*}",
81 m.connect('delete_repo_users_group', "/repos_delete_users_group/{repo_name:.*}",
82 action="delete_perm_users_group", conditions=dict(method=["DELETE"],
82 action="delete_perm_users_group", conditions=dict(method=["DELETE"],
83 function=check_repo))
83 function=check_repo))
84
84
85 #settings actions
85 #settings actions
86 m.connect('repo_stats', "/repos_stats/{repo_name:.*}",
86 m.connect('repo_stats', "/repos_stats/{repo_name:.*}",
87 action="repo_stats", conditions=dict(method=["DELETE"],
87 action="repo_stats", conditions=dict(method=["DELETE"],
88 function=check_repo))
88 function=check_repo))
89 m.connect('repo_cache', "/repos_cache/{repo_name:.*}",
89 m.connect('repo_cache', "/repos_cache/{repo_name:.*}",
90 action="repo_cache", conditions=dict(method=["DELETE"],
90 action="repo_cache", conditions=dict(method=["DELETE"],
91 function=check_repo))
91 function=check_repo))
92 m.connect('repo_public_journal', "/repos_public_journal/{repo_name:.*}",
92 m.connect('repo_public_journal', "/repos_public_journal/{repo_name:.*}",
93 action="repo_public_journal", conditions=dict(method=["PUT"],
93 action="repo_public_journal", conditions=dict(method=["PUT"],
94 function=check_repo))
94 function=check_repo))
95 m.connect('repo_pull', "/repo_pull/{repo_name:.*}",
95 m.connect('repo_pull', "/repo_pull/{repo_name:.*}",
96 action="repo_pull", conditions=dict(method=["PUT"],
96 action="repo_pull", conditions=dict(method=["PUT"],
97 function=check_repo))
97 function=check_repo))
98
98
99 #ADMIN REPOS GROUP REST ROUTES
100 routes_map.resource('repos_group', 'repos_groups', controller='admin/repos_groups', path_prefix='/_admin')
99
101
100 #ADMIN USER REST ROUTES
102 #ADMIN USER REST ROUTES
101 routes_map.resource('user', 'users', controller='admin/users', path_prefix='/_admin')
103 routes_map.resource('user', 'users', controller='admin/users', path_prefix='/_admin')
102
104
103 #ADMIN USER REST ROUTES
105 #ADMIN USERS REST ROUTES
104 routes_map.resource('users_group', 'users_groups', controller='admin/users_groups', path_prefix='/_admin')
106 routes_map.resource('users_group', 'users_groups', controller='admin/users_groups', path_prefix='/_admin')
105
107
106 #ADMIN GROUP REST ROUTES
108 #ADMIN GROUP REST ROUTES
107 routes_map.resource('group', 'groups', controller='admin/groups', path_prefix='/_admin')
109 routes_map.resource('group', 'groups', controller='admin/groups', path_prefix='/_admin')
108
110
109 #ADMIN PERMISSIONS REST ROUTES
111 #ADMIN PERMISSIONS REST ROUTES
110 routes_map.resource('permission', 'permissions', controller='admin/permissions', path_prefix='/_admin')
112 routes_map.resource('permission', 'permissions', controller='admin/permissions', path_prefix='/_admin')
111
113
112 ##ADMIN LDAP SETTINGS
114 ##ADMIN LDAP SETTINGS
113 routes_map.connect('ldap_settings', '/_admin/ldap', controller='admin/ldap_settings',
115 routes_map.connect('ldap_settings', '/_admin/ldap', controller='admin/ldap_settings',
114 action='ldap_settings', conditions=dict(method=["POST"]))
116 action='ldap_settings', conditions=dict(method=["POST"]))
115 routes_map.connect('ldap_home', '/_admin/ldap', controller='admin/ldap_settings',)
117 routes_map.connect('ldap_home', '/_admin/ldap', controller='admin/ldap_settings',)
116
118
117
119
118 #ADMIN SETTINGS REST ROUTES
120 #ADMIN SETTINGS REST ROUTES
119 with routes_map.submapper(path_prefix='/_admin', controller='admin/settings') as m:
121 with routes_map.submapper(path_prefix='/_admin', controller='admin/settings') as m:
120 m.connect("admin_settings", "/settings",
122 m.connect("admin_settings", "/settings",
121 action="create", conditions=dict(method=["POST"]))
123 action="create", conditions=dict(method=["POST"]))
122 m.connect("admin_settings", "/settings",
124 m.connect("admin_settings", "/settings",
123 action="index", conditions=dict(method=["GET"]))
125 action="index", conditions=dict(method=["GET"]))
124 m.connect("formatted_admin_settings", "/settings.{format}",
126 m.connect("formatted_admin_settings", "/settings.{format}",
125 action="index", conditions=dict(method=["GET"]))
127 action="index", conditions=dict(method=["GET"]))
126 m.connect("admin_new_setting", "/settings/new",
128 m.connect("admin_new_setting", "/settings/new",
127 action="new", conditions=dict(method=["GET"]))
129 action="new", conditions=dict(method=["GET"]))
128 m.connect("formatted_admin_new_setting", "/settings/new.{format}",
130 m.connect("formatted_admin_new_setting", "/settings/new.{format}",
129 action="new", conditions=dict(method=["GET"]))
131 action="new", conditions=dict(method=["GET"]))
130 m.connect("/settings/{setting_id}",
132 m.connect("/settings/{setting_id}",
131 action="update", conditions=dict(method=["PUT"]))
133 action="update", conditions=dict(method=["PUT"]))
132 m.connect("/settings/{setting_id}",
134 m.connect("/settings/{setting_id}",
133 action="delete", conditions=dict(method=["DELETE"]))
135 action="delete", conditions=dict(method=["DELETE"]))
134 m.connect("admin_edit_setting", "/settings/{setting_id}/edit",
136 m.connect("admin_edit_setting", "/settings/{setting_id}/edit",
135 action="edit", conditions=dict(method=["GET"]))
137 action="edit", conditions=dict(method=["GET"]))
136 m.connect("formatted_admin_edit_setting", "/settings/{setting_id}.{format}/edit",
138 m.connect("formatted_admin_edit_setting", "/settings/{setting_id}.{format}/edit",
137 action="edit", conditions=dict(method=["GET"]))
139 action="edit", conditions=dict(method=["GET"]))
138 m.connect("admin_setting", "/settings/{setting_id}",
140 m.connect("admin_setting", "/settings/{setting_id}",
139 action="show", conditions=dict(method=["GET"]))
141 action="show", conditions=dict(method=["GET"]))
140 m.connect("formatted_admin_setting", "/settings/{setting_id}.{format}",
142 m.connect("formatted_admin_setting", "/settings/{setting_id}.{format}",
141 action="show", conditions=dict(method=["GET"]))
143 action="show", conditions=dict(method=["GET"]))
142 m.connect("admin_settings_my_account", "/my_account",
144 m.connect("admin_settings_my_account", "/my_account",
143 action="my_account", conditions=dict(method=["GET"]))
145 action="my_account", conditions=dict(method=["GET"]))
144 m.connect("admin_settings_my_account_update", "/my_account_update",
146 m.connect("admin_settings_my_account_update", "/my_account_update",
145 action="my_account_update", conditions=dict(method=["PUT"]))
147 action="my_account_update", conditions=dict(method=["PUT"]))
146 m.connect("admin_settings_create_repository", "/create_repository",
148 m.connect("admin_settings_create_repository", "/create_repository",
147 action="create_repository", conditions=dict(method=["GET"]))
149 action="create_repository", conditions=dict(method=["GET"]))
148
150
149 #ADMIN MAIN PAGES
151 #ADMIN MAIN PAGES
150 with routes_map.submapper(path_prefix='/_admin', controller='admin/admin') as m:
152 with routes_map.submapper(path_prefix='/_admin', controller='admin/admin') as m:
151 m.connect('admin_home', '', action='index')#main page
153 m.connect('admin_home', '', action='index')#main page
152 m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
154 m.connect('admin_add_repo', '/add_repo/{new_repo:[a-z0-9\. _-]*}',
153 action='add_repo')
155 action='add_repo')
154
156
155
157
156 #USER JOURNAL
158 #USER JOURNAL
157 routes_map.connect('journal', '/_admin/journal', controller='journal',)
159 routes_map.connect('journal', '/_admin/journal', controller='journal',)
158 routes_map.connect('public_journal', '/_admin/public_journal', controller='journal', action="public_journal")
160 routes_map.connect('public_journal', '/_admin/public_journal', controller='journal', action="public_journal")
159 routes_map.connect('public_journal_rss', '/_admin/public_journal_rss', controller='journal', action="public_journal_rss")
161 routes_map.connect('public_journal_rss', '/_admin/public_journal_rss', controller='journal', action="public_journal_rss")
160 routes_map.connect('public_journal_atom', '/_admin/public_journal_atom', controller='journal', action="public_journal_atom")
162 routes_map.connect('public_journal_atom', '/_admin/public_journal_atom', controller='journal', action="public_journal_atom")
161
163
162 routes_map.connect('toggle_following', '/_admin/toggle_following', controller='journal',
164 routes_map.connect('toggle_following', '/_admin/toggle_following', controller='journal',
163 action='toggle_following', conditions=dict(method=["POST"]))
165 action='toggle_following', conditions=dict(method=["POST"]))
164
166
165
167
166 #SEARCH
168 #SEARCH
167 routes_map.connect('search', '/_admin/search', controller='search',)
169 routes_map.connect('search', '/_admin/search', controller='search',)
168 routes_map.connect('search_repo', '/_admin/search/{search_repo:.*}', controller='search')
170 routes_map.connect('search_repo', '/_admin/search/{search_repo:.*}', controller='search')
169
171
170 #LOGIN/LOGOUT/REGISTER/SIGN IN
172 #LOGIN/LOGOUT/REGISTER/SIGN IN
171 routes_map.connect('login_home', '/_admin/login', controller='login')
173 routes_map.connect('login_home', '/_admin/login', controller='login')
172 routes_map.connect('logout_home', '/_admin/logout', controller='login', action='logout')
174 routes_map.connect('logout_home', '/_admin/logout', controller='login', action='logout')
173 routes_map.connect('register', '/_admin/register', controller='login', action='register')
175 routes_map.connect('register', '/_admin/register', controller='login', action='register')
174 routes_map.connect('reset_password', '/_admin/password_reset', controller='login', action='password_reset')
176 routes_map.connect('reset_password', '/_admin/password_reset', controller='login', action='password_reset')
175
177
176 #FEEDS
178 #FEEDS
177 routes_map.connect('rss_feed_home', '/{repo_name:.*}/feed/rss',
179 routes_map.connect('rss_feed_home', '/{repo_name:.*}/feed/rss',
178 controller='feed', action='rss',
180 controller='feed', action='rss',
179 conditions=dict(function=check_repo))
181 conditions=dict(function=check_repo))
180 routes_map.connect('atom_feed_home', '/{repo_name:.*}/feed/atom',
182 routes_map.connect('atom_feed_home', '/{repo_name:.*}/feed/atom',
181 controller='feed', action='atom',
183 controller='feed', action='atom',
182 conditions=dict(function=check_repo))
184 conditions=dict(function=check_repo))
183
185
184
186
185 #REPOSITORY ROUTES
187 #REPOSITORY ROUTES
186 routes_map.connect('changeset_home', '/{repo_name:.*}/changeset/{revision}',
188 routes_map.connect('changeset_home', '/{repo_name:.*}/changeset/{revision}',
187 controller='changeset', revision='tip',
189 controller='changeset', revision='tip',
188 conditions=dict(function=check_repo))
190 conditions=dict(function=check_repo))
189 routes_map.connect('raw_changeset_home', '/{repo_name:.*}/raw-changeset/{revision}',
191 routes_map.connect('raw_changeset_home', '/{repo_name:.*}/raw-changeset/{revision}',
190 controller='changeset', action='raw_changeset', revision='tip',
192 controller='changeset', action='raw_changeset', revision='tip',
191 conditions=dict(function=check_repo))
193 conditions=dict(function=check_repo))
192 routes_map.connect('summary_home', '/{repo_name:.*}',
194 routes_map.connect('summary_home', '/{repo_name:.*}',
193 controller='summary', conditions=dict(function=check_repo))
195 controller='summary', conditions=dict(function=check_repo))
194 routes_map.connect('summary_home', '/{repo_name:.*}/summary',
196 routes_map.connect('summary_home', '/{repo_name:.*}/summary',
195 controller='summary', conditions=dict(function=check_repo))
197 controller='summary', conditions=dict(function=check_repo))
196 routes_map.connect('shortlog_home', '/{repo_name:.*}/shortlog',
198 routes_map.connect('shortlog_home', '/{repo_name:.*}/shortlog',
197 controller='shortlog', conditions=dict(function=check_repo))
199 controller='shortlog', conditions=dict(function=check_repo))
198 routes_map.connect('branches_home', '/{repo_name:.*}/branches',
200 routes_map.connect('branches_home', '/{repo_name:.*}/branches',
199 controller='branches', conditions=dict(function=check_repo))
201 controller='branches', conditions=dict(function=check_repo))
200 routes_map.connect('tags_home', '/{repo_name:.*}/tags',
202 routes_map.connect('tags_home', '/{repo_name:.*}/tags',
201 controller='tags', conditions=dict(function=check_repo))
203 controller='tags', conditions=dict(function=check_repo))
202 routes_map.connect('changelog_home', '/{repo_name:.*}/changelog',
204 routes_map.connect('changelog_home', '/{repo_name:.*}/changelog',
203 controller='changelog', conditions=dict(function=check_repo))
205 controller='changelog', conditions=dict(function=check_repo))
204 routes_map.connect('files_home', '/{repo_name:.*}/files/{revision}/{f_path:.*}',
206 routes_map.connect('files_home', '/{repo_name:.*}/files/{revision}/{f_path:.*}',
205 controller='files', revision='tip', f_path='',
207 controller='files', revision='tip', f_path='',
206 conditions=dict(function=check_repo))
208 conditions=dict(function=check_repo))
207 routes_map.connect('files_diff_home', '/{repo_name:.*}/diff/{f_path:.*}',
209 routes_map.connect('files_diff_home', '/{repo_name:.*}/diff/{f_path:.*}',
208 controller='files', action='diff', revision='tip', f_path='',
210 controller='files', action='diff', revision='tip', f_path='',
209 conditions=dict(function=check_repo))
211 conditions=dict(function=check_repo))
210 routes_map.connect('files_rawfile_home', '/{repo_name:.*}/rawfile/{revision}/{f_path:.*}',
212 routes_map.connect('files_rawfile_home', '/{repo_name:.*}/rawfile/{revision}/{f_path:.*}',
211 controller='files', action='rawfile', revision='tip', f_path='',
213 controller='files', action='rawfile', revision='tip', f_path='',
212 conditions=dict(function=check_repo))
214 conditions=dict(function=check_repo))
213 routes_map.connect('files_raw_home', '/{repo_name:.*}/raw/{revision}/{f_path:.*}',
215 routes_map.connect('files_raw_home', '/{repo_name:.*}/raw/{revision}/{f_path:.*}',
214 controller='files', action='raw', revision='tip', f_path='',
216 controller='files', action='raw', revision='tip', f_path='',
215 conditions=dict(function=check_repo))
217 conditions=dict(function=check_repo))
216 routes_map.connect('files_annotate_home', '/{repo_name:.*}/annotate/{revision}/{f_path:.*}',
218 routes_map.connect('files_annotate_home', '/{repo_name:.*}/annotate/{revision}/{f_path:.*}',
217 controller='files', action='annotate', revision='tip', f_path='',
219 controller='files', action='annotate', revision='tip', f_path='',
218 conditions=dict(function=check_repo))
220 conditions=dict(function=check_repo))
219 routes_map.connect('files_archive_home', '/{repo_name:.*}/archive/{fname}',
221 routes_map.connect('files_archive_home', '/{repo_name:.*}/archive/{fname}',
220 controller='files', action='archivefile',
222 controller='files', action='archivefile',
221 conditions=dict(function=check_repo))
223 conditions=dict(function=check_repo))
222 routes_map.connect('repo_settings_delete', '/{repo_name:.*}/settings',
224 routes_map.connect('repo_settings_delete', '/{repo_name:.*}/settings',
223 controller='settings', action="delete",
225 controller='settings', action="delete",
224 conditions=dict(method=["DELETE"], function=check_repo))
226 conditions=dict(method=["DELETE"], function=check_repo))
225 routes_map.connect('repo_settings_update', '/{repo_name:.*}/settings',
227 routes_map.connect('repo_settings_update', '/{repo_name:.*}/settings',
226 controller='settings', action="update",
228 controller='settings', action="update",
227 conditions=dict(method=["PUT"], function=check_repo))
229 conditions=dict(method=["PUT"], function=check_repo))
228 routes_map.connect('repo_settings_home', '/{repo_name:.*}/settings',
230 routes_map.connect('repo_settings_home', '/{repo_name:.*}/settings',
229 controller='settings', action='index',
231 controller='settings', action='index',
230 conditions=dict(function=check_repo))
232 conditions=dict(function=check_repo))
231
233
232 routes_map.connect('repo_fork_create_home', '/{repo_name:.*}/fork',
234 routes_map.connect('repo_fork_create_home', '/{repo_name:.*}/fork',
233 controller='settings', action='fork_create',
235 controller='settings', action='fork_create',
234 conditions=dict(function=check_repo, method=["POST"]))
236 conditions=dict(function=check_repo, method=["POST"]))
235 routes_map.connect('repo_fork_home', '/{repo_name:.*}/fork',
237 routes_map.connect('repo_fork_home', '/{repo_name:.*}/fork',
236 controller='settings', action='fork',
238 controller='settings', action='fork',
237 conditions=dict(function=check_repo))
239 conditions=dict(function=check_repo))
238
240
239 return routes_map
241 return routes_map
@@ -1,169 +1,171 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.summary
3 rhodecode.controllers.summary
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Summary controller for Rhodecode
6 Summary controller for Rhodecode
7
7
8 :created_on: Apr 18, 2010
8 :created_on: Apr 18, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software; you can redistribute it and/or
13 # This program is free software; you can redistribute it and/or
14 # modify it under the terms of the GNU General Public License
14 # modify it under the terms of the GNU General Public License
15 # as published by the Free Software Foundation; version 2
15 # as published by the Free Software Foundation; version 2
16 # of the License or (at your opinion) any later version of the license.
16 # of the License or (at your opinion) any later version of the license.
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, write to the Free Software
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # MA 02110-1301, USA.
26 # MA 02110-1301, USA.
27
27
28 import calendar
28 import calendar
29 import logging
29 import logging
30 from time import mktime
30 from time import mktime
31 from datetime import datetime, timedelta, date
31 from datetime import datetime, timedelta, date
32
32
33 from vcs.exceptions import ChangesetError
33 from vcs.exceptions import ChangesetError
34
34
35 from pylons import tmpl_context as c, request, url
35 from pylons import tmpl_context as c, request, url
36 from pylons.i18n.translation import _
36 from pylons.i18n.translation import _
37
37
38 from rhodecode.model.db import Statistics
38 from rhodecode.model.db import Statistics, Repository
39 from rhodecode.model.repo import RepoModel
39
40
40 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
41 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
41 from rhodecode.lib.base import BaseRepoController, render
42 from rhodecode.lib.base import BaseRepoController, render
42 from rhodecode.lib.utils import OrderedDict, EmptyChangeset
43 from rhodecode.lib.utils import OrderedDict, EmptyChangeset
43
44
44 from rhodecode.lib.celerylib import run_task
45 from rhodecode.lib.celerylib import run_task
45 from rhodecode.lib.celerylib.tasks import get_commits_stats
46 from rhodecode.lib.celerylib.tasks import get_commits_stats
46 from rhodecode.lib.helpers import RepoPage
47 from rhodecode.lib.helpers import RepoPage
47
48
48 try:
49 try:
49 import json
50 import json
50 except ImportError:
51 except ImportError:
51 #python 2.5 compatibility
52 #python 2.5 compatibility
52 import simplejson as json
53 import simplejson as json
53 log = logging.getLogger(__name__)
54 log = logging.getLogger(__name__)
54
55
55 class SummaryController(BaseRepoController):
56 class SummaryController(BaseRepoController):
56
57
57 @LoginRequired()
58 @LoginRequired()
58 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
59 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
59 'repository.admin')
60 'repository.admin')
60 def __before__(self):
61 def __before__(self):
61 super(SummaryController, self).__before__()
62 super(SummaryController, self).__before__()
62
63
63 def index(self):
64 def index(self, repo_name):
64 c.repo, dbrepo = self.scm_model.get(c.repo_name)
65
65 c.dbrepo = dbrepo
66 e = request.environ
67 c.dbrepo = dbrepo = Repository.by_repo_name(repo_name)
66
68
67 c.following = self.scm_model.is_following_repo(c.repo_name,
69 c.following = self.scm_model.is_following_repo(repo_name,
68 self.rhodecode_user.user_id)
70 self.rhodecode_user.user_id)
69 def url_generator(**kw):
71 def url_generator(**kw):
70 return url('shortlog_home', repo_name=c.repo_name, **kw)
72 return url('shortlog_home', repo_name=repo_name, **kw)
71
73
72 c.repo_changesets = RepoPage(c.repo, page=1, items_per_page=10,
74 c.repo_changesets = RepoPage(c.rhodecode_repo, page=1, items_per_page=10,
73 url=url_generator)
75 url=url_generator)
74
76
75 e = request.environ
77
76
78
77 if self.rhodecode_user.username == 'default':
79 if self.rhodecode_user.username == 'default':
78 #for default(anonymous) user we don't need to pass credentials
80 #for default(anonymous) user we don't need to pass credentials
79 username = ''
81 username = ''
80 password = ''
82 password = ''
81 else:
83 else:
82 username = str(self.rhodecode_user.username)
84 username = str(self.rhodecode_user.username)
83 password = '@'
85 password = '@'
84
86
85 uri = u'%(protocol)s://%(user)s%(password)s%(host)s%(prefix)s/%(repo_name)s' % {
87 uri = u'%(protocol)s://%(user)s%(password)s%(host)s%(prefix)s/%(repo_name)s' % {
86 'protocol': e.get('wsgi.url_scheme'),
88 'protocol': e.get('wsgi.url_scheme'),
87 'user':username,
89 'user':username,
88 'password':password,
90 'password':password,
89 'host':e.get('HTTP_HOST'),
91 'host':e.get('HTTP_HOST'),
90 'prefix':e.get('SCRIPT_NAME'),
92 'prefix':e.get('SCRIPT_NAME'),
91 'repo_name':c.repo_name, }
93 'repo_name':repo_name, }
92 c.clone_repo_url = uri
94 c.clone_repo_url = uri
93 c.repo_tags = OrderedDict()
95 c.repo_tags = OrderedDict()
94 for name, hash in c.repo.tags.items()[:10]:
96 for name, hash in c.rhodecode_repo.tags.items()[:10]:
95 try:
97 try:
96 c.repo_tags[name] = c.repo.get_changeset(hash)
98 c.repo_tags[name] = c.rhodecode_repo.get_changeset(hash)
97 except ChangesetError:
99 except ChangesetError:
98 c.repo_tags[name] = EmptyChangeset(hash)
100 c.repo_tags[name] = EmptyChangeset(hash)
99
101
100 c.repo_branches = OrderedDict()
102 c.repo_branches = OrderedDict()
101 for name, hash in c.repo.branches.items()[:10]:
103 for name, hash in c.rhodecode_repo.branches.items()[:10]:
102 try:
104 try:
103 c.repo_branches[name] = c.repo.get_changeset(hash)
105 c.repo_branches[name] = c.rhodecode_repo.get_changeset(hash)
104 except ChangesetError:
106 except ChangesetError:
105 c.repo_branches[name] = EmptyChangeset(hash)
107 c.repo_branches[name] = EmptyChangeset(hash)
106
108
107 td = date.today() + timedelta(days=1)
109 td = date.today() + timedelta(days=1)
108 td_1m = td - timedelta(days=calendar.mdays[td.month])
110 td_1m = td - timedelta(days=calendar.mdays[td.month])
109 td_1y = td - timedelta(days=365)
111 td_1y = td - timedelta(days=365)
110
112
111 ts_min_m = mktime(td_1m.timetuple())
113 ts_min_m = mktime(td_1m.timetuple())
112 ts_min_y = mktime(td_1y.timetuple())
114 ts_min_y = mktime(td_1y.timetuple())
113 ts_max_y = mktime(td.timetuple())
115 ts_max_y = mktime(td.timetuple())
114
116
115 if dbrepo.enable_statistics:
117 if dbrepo.enable_statistics:
116 c.no_data_msg = _('No data loaded yet')
118 c.no_data_msg = _('No data loaded yet')
117 run_task(get_commits_stats, c.repo.name, ts_min_y, ts_max_y)
119 run_task(get_commits_stats, c.dbrepo.repo_name, ts_min_y, ts_max_y)
118 else:
120 else:
119 c.no_data_msg = _('Statistics are disabled for this repository')
121 c.no_data_msg = _('Statistics are disabled for this repository')
120 c.ts_min = ts_min_m
122 c.ts_min = ts_min_m
121 c.ts_max = ts_max_y
123 c.ts_max = ts_max_y
122
124
123 stats = self.sa.query(Statistics)\
125 stats = self.sa.query(Statistics)\
124 .filter(Statistics.repository == dbrepo)\
126 .filter(Statistics.repository == dbrepo)\
125 .scalar()
127 .scalar()
126
128
127
129
128 if stats and stats.languages:
130 if stats and stats.languages:
129 c.no_data = False is dbrepo.enable_statistics
131 c.no_data = False is dbrepo.enable_statistics
130 lang_stats = json.loads(stats.languages)
132 lang_stats = json.loads(stats.languages)
131 c.commit_data = stats.commit_activity
133 c.commit_data = stats.commit_activity
132 c.overview_data = stats.commit_activity_combined
134 c.overview_data = stats.commit_activity_combined
133 c.trending_languages = json.dumps(OrderedDict(
135 c.trending_languages = json.dumps(OrderedDict(
134 sorted(lang_stats.items(), reverse=True,
136 sorted(lang_stats.items(), reverse=True,
135 key=lambda k: k[1])[:10]
137 key=lambda k: k[1])[:10]
136 )
138 )
137 )
139 )
138 else:
140 else:
139 c.commit_data = json.dumps({})
141 c.commit_data = json.dumps({})
140 c.overview_data = json.dumps([[ts_min_y, 0], [ts_max_y, 10] ])
142 c.overview_data = json.dumps([[ts_min_y, 0], [ts_max_y, 10] ])
141 c.trending_languages = json.dumps({})
143 c.trending_languages = json.dumps({})
142 c.no_data = True
144 c.no_data = True
143
145
144 c.enable_downloads = dbrepo.enable_downloads
146 c.enable_downloads = dbrepo.enable_downloads
145 if c.enable_downloads:
147 if c.enable_downloads:
146 c.download_options = self._get_download_links(c.repo)
148 c.download_options = self._get_download_links(c.rhodecode_repo)
147
149
148 return render('summary/summary.html')
150 return render('summary/summary.html')
149
151
150
152
151
153
152 def _get_download_links(self, repo):
154 def _get_download_links(self, repo):
153
155
154 download_l = []
156 download_l = []
155
157
156 branches_group = ([], _("Branches"))
158 branches_group = ([], _("Branches"))
157 tags_group = ([], _("Tags"))
159 tags_group = ([], _("Tags"))
158
160
159 for name, chs in c.rhodecode_repo.branches.items():
161 for name, chs in c.rhodecode_repo.branches.items():
160 #chs = chs.split(':')[-1]
162 #chs = chs.split(':')[-1]
161 branches_group[0].append((chs, name),)
163 branches_group[0].append((chs, name),)
162 download_l.append(branches_group)
164 download_l.append(branches_group)
163
165
164 for name, chs in c.rhodecode_repo.tags.items():
166 for name, chs in c.rhodecode_repo.tags.items():
165 #chs = chs.split(':')[-1]
167 #chs = chs.split(':')[-1]
166 tags_group[0].append((chs, name),)
168 tags_group[0].append((chs, name),)
167 download_l.append(tags_group)
169 download_l.append(tags_group)
168
170
169 return download_l
171 return download_l
@@ -1,697 +1,700 b''
1 """Helper functions
1 """Helper functions
2
2
3 Consists of functions to typically be used within templates, but also
3 Consists of functions to typically be used within templates, but also
4 available to Controllers. This module is available to both as 'h'.
4 available to Controllers. This module is available to both as 'h'.
5 """
5 """
6 import random
6 import random
7 import hashlib
7 import hashlib
8 import StringIO
8 import StringIO
9 import urllib
9 import urllib
10
10
11 from datetime import datetime
11 from datetime import datetime
12 from pygments.formatters import HtmlFormatter
12 from pygments.formatters import HtmlFormatter
13 from pygments import highlight as code_highlight
13 from pygments import highlight as code_highlight
14 from pylons import url, request, config
14 from pylons import url, request, config
15 from pylons.i18n.translation import _, ungettext
15 from pylons.i18n.translation import _, ungettext
16
16
17 from webhelpers.html import literal, HTML, escape
17 from webhelpers.html import literal, HTML, escape
18 from webhelpers.html.tools import *
18 from webhelpers.html.tools import *
19 from webhelpers.html.builder import make_tag
19 from webhelpers.html.builder import make_tag
20 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
20 from webhelpers.html.tags import auto_discovery_link, checkbox, css_classes, \
21 end_form, file, form, hidden, image, javascript_link, link_to, link_to_if, \
21 end_form, file, form, hidden, image, javascript_link, link_to, link_to_if, \
22 link_to_unless, ol, required_legend, select, stylesheet_link, submit, text, \
22 link_to_unless, ol, required_legend, select, stylesheet_link, submit, text, \
23 password, textarea, title, ul, xml_declaration, radio
23 password, textarea, title, ul, xml_declaration, radio
24 from webhelpers.html.tools import auto_link, button_to, highlight, js_obfuscate, \
24 from webhelpers.html.tools import auto_link, button_to, highlight, js_obfuscate, \
25 mail_to, strip_links, strip_tags, tag_re
25 mail_to, strip_links, strip_tags, tag_re
26 from webhelpers.number import format_byte_size, format_bit_size
26 from webhelpers.number import format_byte_size, format_bit_size
27 from webhelpers.pylonslib import Flash as _Flash
27 from webhelpers.pylonslib import Flash as _Flash
28 from webhelpers.pylonslib.secure_form import secure_form
28 from webhelpers.pylonslib.secure_form import secure_form
29 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
29 from webhelpers.text import chop_at, collapse, convert_accented_entities, \
30 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
30 convert_misc_entities, lchop, plural, rchop, remove_formatting, \
31 replace_whitespace, urlify, truncate, wrap_paragraphs
31 replace_whitespace, urlify, truncate, wrap_paragraphs
32 from webhelpers.date import time_ago_in_words
32 from webhelpers.date import time_ago_in_words
33 from webhelpers.paginate import Page
33 from webhelpers.paginate import Page
34 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
34 from webhelpers.html.tags import _set_input_attrs, _set_id_attr, \
35 convert_boolean_attrs, NotGiven
35 convert_boolean_attrs, NotGiven
36
36
37 from vcs.utils.annotate import annotate_highlight
37 from vcs.utils.annotate import annotate_highlight
38 from rhodecode.lib.utils import repo_name_slug
38 from rhodecode.lib.utils import repo_name_slug
39 from rhodecode.lib import str2bool
39 from rhodecode.lib import str2bool
40
40
41 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
41 def _reset(name, value=None, id=NotGiven, type="reset", **attrs):
42 """
42 """
43 Reset button
43 Reset button
44 """
44 """
45 _set_input_attrs(attrs, type, name, value)
45 _set_input_attrs(attrs, type, name, value)
46 _set_id_attr(attrs, id, name)
46 _set_id_attr(attrs, id, name)
47 convert_boolean_attrs(attrs, ["disabled"])
47 convert_boolean_attrs(attrs, ["disabled"])
48 return HTML.input(**attrs)
48 return HTML.input(**attrs)
49
49
50 reset = _reset
50 reset = _reset
51
51
52
52
53 def get_token():
53 def get_token():
54 """Return the current authentication token, creating one if one doesn't
54 """Return the current authentication token, creating one if one doesn't
55 already exist.
55 already exist.
56 """
56 """
57 token_key = "_authentication_token"
57 token_key = "_authentication_token"
58 from pylons import session
58 from pylons import session
59 if not token_key in session:
59 if not token_key in session:
60 try:
60 try:
61 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
61 token = hashlib.sha1(str(random.getrandbits(128))).hexdigest()
62 except AttributeError: # Python < 2.4
62 except AttributeError: # Python < 2.4
63 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
63 token = hashlib.sha1(str(random.randrange(2 ** 128))).hexdigest()
64 session[token_key] = token
64 session[token_key] = token
65 if hasattr(session, 'save'):
65 if hasattr(session, 'save'):
66 session.save()
66 session.save()
67 return session[token_key]
67 return session[token_key]
68
68
69 class _GetError(object):
69 class _GetError(object):
70 """Get error from form_errors, and represent it as span wrapped error
70 """Get error from form_errors, and represent it as span wrapped error
71 message
71 message
72
72
73 :param field_name: field to fetch errors for
73 :param field_name: field to fetch errors for
74 :param form_errors: form errors dict
74 :param form_errors: form errors dict
75 """
75 """
76
76
77 def __call__(self, field_name, form_errors):
77 def __call__(self, field_name, form_errors):
78 tmpl = """<span class="error_msg">%s</span>"""
78 tmpl = """<span class="error_msg">%s</span>"""
79 if form_errors and form_errors.has_key(field_name):
79 if form_errors and form_errors.has_key(field_name):
80 return literal(tmpl % form_errors.get(field_name))
80 return literal(tmpl % form_errors.get(field_name))
81
81
82 get_error = _GetError()
82 get_error = _GetError()
83
83
84 class _ToolTip(object):
84 class _ToolTip(object):
85
85
86 def __call__(self, tooltip_title, trim_at=50):
86 def __call__(self, tooltip_title, trim_at=50):
87 """Special function just to wrap our text into nice formatted
87 """Special function just to wrap our text into nice formatted
88 autowrapped text
88 autowrapped text
89
89
90 :param tooltip_title:
90 :param tooltip_title:
91 """
91 """
92
92
93 return wrap_paragraphs(escape(tooltip_title), trim_at)\
93 return wrap_paragraphs(escape(tooltip_title), trim_at)\
94 .replace('\n', '<br/>')
94 .replace('\n', '<br/>')
95
95
96 def activate(self):
96 def activate(self):
97 """Adds tooltip mechanism to the given Html all tooltips have to have
97 """Adds tooltip mechanism to the given Html all tooltips have to have
98 set class `tooltip` and set attribute `tooltip_title`.
98 set class `tooltip` and set attribute `tooltip_title`.
99 Then a tooltip will be generated based on that. All with yui js tooltip
99 Then a tooltip will be generated based on that. All with yui js tooltip
100 """
100 """
101
101
102 js = '''
102 js = '''
103 YAHOO.util.Event.onDOMReady(function(){
103 YAHOO.util.Event.onDOMReady(function(){
104 function toolTipsId(){
104 function toolTipsId(){
105 var ids = [];
105 var ids = [];
106 var tts = YAHOO.util.Dom.getElementsByClassName('tooltip');
106 var tts = YAHOO.util.Dom.getElementsByClassName('tooltip');
107
107
108 for (var i = 0; i < tts.length; i++) {
108 for (var i = 0; i < tts.length; i++) {
109 //if element doesn't not have and id autogenerate one for tooltip
109 //if element doesn't not have and id autogenerate one for tooltip
110
110
111 if (!tts[i].id){
111 if (!tts[i].id){
112 tts[i].id='tt'+i*100;
112 tts[i].id='tt'+i*100;
113 }
113 }
114 ids.push(tts[i].id);
114 ids.push(tts[i].id);
115 }
115 }
116 return ids
116 return ids
117 };
117 };
118 var myToolTips = new YAHOO.widget.Tooltip("tooltip", {
118 var myToolTips = new YAHOO.widget.Tooltip("tooltip", {
119 context: toolTipsId(),
119 context: toolTipsId(),
120 monitorresize:false,
120 monitorresize:false,
121 xyoffset :[0,0],
121 xyoffset :[0,0],
122 autodismissdelay:300000,
122 autodismissdelay:300000,
123 hidedelay:5,
123 hidedelay:5,
124 showdelay:20,
124 showdelay:20,
125 });
125 });
126
126
127 // Set the text for the tooltip just before we display it. Lazy method
127 // Set the text for the tooltip just before we display it. Lazy method
128 myToolTips.contextTriggerEvent.subscribe(
128 myToolTips.contextTriggerEvent.subscribe(
129 function(type, args) {
129 function(type, args) {
130
130
131 var context = args[0];
131 var context = args[0];
132
132
133 //positioning of tooltip
133 //positioning of tooltip
134 var tt_w = this.element.clientWidth;//tooltip width
134 var tt_w = this.element.clientWidth;//tooltip width
135 var tt_h = this.element.clientHeight;//tooltip height
135 var tt_h = this.element.clientHeight;//tooltip height
136
136
137 var context_w = context.offsetWidth;
137 var context_w = context.offsetWidth;
138 var context_h = context.offsetHeight;
138 var context_h = context.offsetHeight;
139
139
140 var pos_x = YAHOO.util.Dom.getX(context);
140 var pos_x = YAHOO.util.Dom.getX(context);
141 var pos_y = YAHOO.util.Dom.getY(context);
141 var pos_y = YAHOO.util.Dom.getY(context);
142
142
143 var display_strategy = 'right';
143 var display_strategy = 'right';
144 var xy_pos = [0,0];
144 var xy_pos = [0,0];
145 switch (display_strategy){
145 switch (display_strategy){
146
146
147 case 'top':
147 case 'top':
148 var cur_x = (pos_x+context_w/2)-(tt_w/2);
148 var cur_x = (pos_x+context_w/2)-(tt_w/2);
149 var cur_y = (pos_y-tt_h-4);
149 var cur_y = (pos_y-tt_h-4);
150 xy_pos = [cur_x,cur_y];
150 xy_pos = [cur_x,cur_y];
151 break;
151 break;
152 case 'bottom':
152 case 'bottom':
153 var cur_x = (pos_x+context_w/2)-(tt_w/2);
153 var cur_x = (pos_x+context_w/2)-(tt_w/2);
154 var cur_y = pos_y+context_h+4;
154 var cur_y = pos_y+context_h+4;
155 xy_pos = [cur_x,cur_y];
155 xy_pos = [cur_x,cur_y];
156 break;
156 break;
157 case 'left':
157 case 'left':
158 var cur_x = (pos_x-tt_w-4);
158 var cur_x = (pos_x-tt_w-4);
159 var cur_y = pos_y-((tt_h/2)-context_h/2);
159 var cur_y = pos_y-((tt_h/2)-context_h/2);
160 xy_pos = [cur_x,cur_y];
160 xy_pos = [cur_x,cur_y];
161 break;
161 break;
162 case 'right':
162 case 'right':
163 var cur_x = (pos_x+context_w+4);
163 var cur_x = (pos_x+context_w+4);
164 var cur_y = pos_y-((tt_h/2)-context_h/2);
164 var cur_y = pos_y-((tt_h/2)-context_h/2);
165 xy_pos = [cur_x,cur_y];
165 xy_pos = [cur_x,cur_y];
166 break;
166 break;
167 default:
167 default:
168 var cur_x = (pos_x+context_w/2)-(tt_w/2);
168 var cur_x = (pos_x+context_w/2)-(tt_w/2);
169 var cur_y = pos_y-tt_h-4;
169 var cur_y = pos_y-tt_h-4;
170 xy_pos = [cur_x,cur_y];
170 xy_pos = [cur_x,cur_y];
171 break;
171 break;
172
172
173 }
173 }
174
174
175 this.cfg.setProperty("xy",xy_pos);
175 this.cfg.setProperty("xy",xy_pos);
176
176
177 });
177 });
178
178
179 //Mouse out
179 //Mouse out
180 myToolTips.contextMouseOutEvent.subscribe(
180 myToolTips.contextMouseOutEvent.subscribe(
181 function(type, args) {
181 function(type, args) {
182 var context = args[0];
182 var context = args[0];
183
183
184 });
184 });
185 });
185 });
186 '''
186 '''
187 return literal(js)
187 return literal(js)
188
188
189 tooltip = _ToolTip()
189 tooltip = _ToolTip()
190
190
191 class _FilesBreadCrumbs(object):
191 class _FilesBreadCrumbs(object):
192
192
193 def __call__(self, repo_name, rev, paths):
193 def __call__(self, repo_name, rev, paths):
194 if isinstance(paths, str):
194 if isinstance(paths, str):
195 paths = paths.decode('utf-8', 'replace')
195 paths = paths.decode('utf-8', 'replace')
196 url_l = [link_to(repo_name, url('files_home',
196 url_l = [link_to(repo_name, url('files_home',
197 repo_name=repo_name,
197 repo_name=repo_name,
198 revision=rev, f_path=''))]
198 revision=rev, f_path=''))]
199 paths_l = paths.split('/')
199 paths_l = paths.split('/')
200 for cnt, p in enumerate(paths_l):
200 for cnt, p in enumerate(paths_l):
201 if p != '':
201 if p != '':
202 url_l.append(link_to(p, url('files_home',
202 url_l.append(link_to(p, url('files_home',
203 repo_name=repo_name,
203 repo_name=repo_name,
204 revision=rev,
204 revision=rev,
205 f_path='/'.join(paths_l[:cnt + 1]))))
205 f_path='/'.join(paths_l[:cnt + 1]))))
206
206
207 return literal('/'.join(url_l))
207 return literal('/'.join(url_l))
208
208
209 files_breadcrumbs = _FilesBreadCrumbs()
209 files_breadcrumbs = _FilesBreadCrumbs()
210
210
211 class CodeHtmlFormatter(HtmlFormatter):
211 class CodeHtmlFormatter(HtmlFormatter):
212 """My code Html Formatter for source codes
212 """My code Html Formatter for source codes
213 """
213 """
214
214
215 def wrap(self, source, outfile):
215 def wrap(self, source, outfile):
216 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
216 return self._wrap_div(self._wrap_pre(self._wrap_code(source)))
217
217
218 def _wrap_code(self, source):
218 def _wrap_code(self, source):
219 for cnt, it in enumerate(source):
219 for cnt, it in enumerate(source):
220 i, t = it
220 i, t = it
221 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
221 t = '<div id="L%s">%s</div>' % (cnt + 1, t)
222 yield i, t
222 yield i, t
223
223
224 def _wrap_tablelinenos(self, inner):
224 def _wrap_tablelinenos(self, inner):
225 dummyoutfile = StringIO.StringIO()
225 dummyoutfile = StringIO.StringIO()
226 lncount = 0
226 lncount = 0
227 for t, line in inner:
227 for t, line in inner:
228 if t:
228 if t:
229 lncount += 1
229 lncount += 1
230 dummyoutfile.write(line)
230 dummyoutfile.write(line)
231
231
232 fl = self.linenostart
232 fl = self.linenostart
233 mw = len(str(lncount + fl - 1))
233 mw = len(str(lncount + fl - 1))
234 sp = self.linenospecial
234 sp = self.linenospecial
235 st = self.linenostep
235 st = self.linenostep
236 la = self.lineanchors
236 la = self.lineanchors
237 aln = self.anchorlinenos
237 aln = self.anchorlinenos
238 nocls = self.noclasses
238 nocls = self.noclasses
239 if sp:
239 if sp:
240 lines = []
240 lines = []
241
241
242 for i in range(fl, fl + lncount):
242 for i in range(fl, fl + lncount):
243 if i % st == 0:
243 if i % st == 0:
244 if i % sp == 0:
244 if i % sp == 0:
245 if aln:
245 if aln:
246 lines.append('<a href="#%s%d" class="special">%*d</a>' %
246 lines.append('<a href="#%s%d" class="special">%*d</a>' %
247 (la, i, mw, i))
247 (la, i, mw, i))
248 else:
248 else:
249 lines.append('<span class="special">%*d</span>' % (mw, i))
249 lines.append('<span class="special">%*d</span>' % (mw, i))
250 else:
250 else:
251 if aln:
251 if aln:
252 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
252 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
253 else:
253 else:
254 lines.append('%*d' % (mw, i))
254 lines.append('%*d' % (mw, i))
255 else:
255 else:
256 lines.append('')
256 lines.append('')
257 ls = '\n'.join(lines)
257 ls = '\n'.join(lines)
258 else:
258 else:
259 lines = []
259 lines = []
260 for i in range(fl, fl + lncount):
260 for i in range(fl, fl + lncount):
261 if i % st == 0:
261 if i % st == 0:
262 if aln:
262 if aln:
263 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
263 lines.append('<a href="#%s%d">%*d</a>' % (la, i, mw, i))
264 else:
264 else:
265 lines.append('%*d' % (mw, i))
265 lines.append('%*d' % (mw, i))
266 else:
266 else:
267 lines.append('')
267 lines.append('')
268 ls = '\n'.join(lines)
268 ls = '\n'.join(lines)
269
269
270 # in case you wonder about the seemingly redundant <div> here: since the
270 # in case you wonder about the seemingly redundant <div> here: since the
271 # content in the other cell also is wrapped in a div, some browsers in
271 # content in the other cell also is wrapped in a div, some browsers in
272 # some configurations seem to mess up the formatting...
272 # some configurations seem to mess up the formatting...
273 if nocls:
273 if nocls:
274 yield 0, ('<table class="%stable">' % self.cssclass +
274 yield 0, ('<table class="%stable">' % self.cssclass +
275 '<tr><td><div class="linenodiv" '
275 '<tr><td><div class="linenodiv" '
276 'style="background-color: #f0f0f0; padding-right: 10px">'
276 'style="background-color: #f0f0f0; padding-right: 10px">'
277 '<pre style="line-height: 125%">' +
277 '<pre style="line-height: 125%">' +
278 ls + '</pre></div></td><td class="code">')
278 ls + '</pre></div></td><td class="code">')
279 else:
279 else:
280 yield 0, ('<table class="%stable">' % self.cssclass +
280 yield 0, ('<table class="%stable">' % self.cssclass +
281 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
281 '<tr><td class="linenos"><div class="linenodiv"><pre>' +
282 ls + '</pre></div></td><td class="code">')
282 ls + '</pre></div></td><td class="code">')
283 yield 0, dummyoutfile.getvalue()
283 yield 0, dummyoutfile.getvalue()
284 yield 0, '</td></tr></table>'
284 yield 0, '</td></tr></table>'
285
285
286
286
287 def pygmentize(filenode, **kwargs):
287 def pygmentize(filenode, **kwargs):
288 """pygmentize function using pygments
288 """pygmentize function using pygments
289
289
290 :param filenode:
290 :param filenode:
291 """
291 """
292
292
293 return literal(code_highlight(filenode.content,
293 return literal(code_highlight(filenode.content,
294 filenode.lexer, CodeHtmlFormatter(**kwargs)))
294 filenode.lexer, CodeHtmlFormatter(**kwargs)))
295
295
296 def pygmentize_annotation(filenode, **kwargs):
296 def pygmentize_annotation(repo_name, filenode, **kwargs):
297 """pygmentize function for annotation
297 """pygmentize function for annotation
298
298
299 :param filenode:
299 :param filenode:
300 """
300 """
301
301
302 color_dict = {}
302 color_dict = {}
303 def gen_color(n=10000):
303 def gen_color(n=10000):
304 """generator for getting n of evenly distributed colors using
304 """generator for getting n of evenly distributed colors using
305 hsv color and golden ratio. It always return same order of colors
305 hsv color and golden ratio. It always return same order of colors
306
306
307 :returns: RGB tuple
307 :returns: RGB tuple
308 """
308 """
309 import colorsys
309 import colorsys
310 golden_ratio = 0.618033988749895
310 golden_ratio = 0.618033988749895
311 h = 0.22717784590367374
311 h = 0.22717784590367374
312
312
313 for c in xrange(n):
313 for c in xrange(n):
314 h += golden_ratio
314 h += golden_ratio
315 h %= 1
315 h %= 1
316 HSV_tuple = [h, 0.95, 0.95]
316 HSV_tuple = [h, 0.95, 0.95]
317 RGB_tuple = colorsys.hsv_to_rgb(*HSV_tuple)
317 RGB_tuple = colorsys.hsv_to_rgb(*HSV_tuple)
318 yield map(lambda x:str(int(x * 256)), RGB_tuple)
318 yield map(lambda x:str(int(x * 256)), RGB_tuple)
319
319
320 cgenerator = gen_color()
320 cgenerator = gen_color()
321
321
322 def get_color_string(cs):
322 def get_color_string(cs):
323 if color_dict.has_key(cs):
323 if color_dict.has_key(cs):
324 col = color_dict[cs]
324 col = color_dict[cs]
325 else:
325 else:
326 col = color_dict[cs] = cgenerator.next()
326 col = color_dict[cs] = cgenerator.next()
327 return "color: rgb(%s)! important;" % (', '.join(col))
327 return "color: rgb(%s)! important;" % (', '.join(col))
328
328
329 def url_func(changeset):
329 def url_func(repo_name):
330 tooltip_html = "<div style='font-size:0.8em'><b>Author:</b>" + \
330 def _url_func(changeset):
331 " %s<br/><b>Date:</b> %s</b><br/><b>Message:</b> %s<br/></div>"
331 tooltip_html = "<div style='font-size:0.8em'><b>Author:</b>" + \
332 " %s<br/><b>Date:</b> %s</b><br/><b>Message:</b> %s<br/></div>"
332
333
333 tooltip_html = tooltip_html % (changeset.author,
334 tooltip_html = tooltip_html % (changeset.author,
334 changeset.date,
335 changeset.date,
335 tooltip(changeset.message))
336 tooltip(changeset.message))
336 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
337 lnk_format = '%5s:%s' % ('r%s' % changeset.revision,
337 short_id(changeset.raw_id))
338 short_id(changeset.raw_id))
338 uri = link_to(
339 uri = link_to(
339 lnk_format,
340 lnk_format,
340 url('changeset_home', repo_name=changeset.repository.name,
341 url('changeset_home', repo_name=repo_name,
341 revision=changeset.raw_id),
342 revision=changeset.raw_id),
342 style=get_color_string(changeset.raw_id),
343 style=get_color_string(changeset.raw_id),
343 class_='tooltip',
344 class_='tooltip',
344 title=tooltip_html
345 title=tooltip_html
345 )
346 )
346
347
347 uri += '\n'
348 uri += '\n'
348 return uri
349 return uri
349 return literal(annotate_highlight(filenode, url_func, **kwargs))
350 return _url_func
351
352 return literal(annotate_highlight(filenode, url_func(repo_name), **kwargs))
350
353
351 def get_changeset_safe(repo, rev):
354 def get_changeset_safe(repo, rev):
352 from vcs.backends.base import BaseRepository
355 from vcs.backends.base import BaseRepository
353 from vcs.exceptions import RepositoryError
356 from vcs.exceptions import RepositoryError
354 if not isinstance(repo, BaseRepository):
357 if not isinstance(repo, BaseRepository):
355 raise Exception('You must pass an Repository '
358 raise Exception('You must pass an Repository '
356 'object as first argument got %s', type(repo))
359 'object as first argument got %s', type(repo))
357
360
358 try:
361 try:
359 cs = repo.get_changeset(rev)
362 cs = repo.get_changeset(rev)
360 except RepositoryError:
363 except RepositoryError:
361 from rhodecode.lib.utils import EmptyChangeset
364 from rhodecode.lib.utils import EmptyChangeset
362 cs = EmptyChangeset()
365 cs = EmptyChangeset()
363 return cs
366 return cs
364
367
365
368
366 def is_following_repo(repo_name, user_id):
369 def is_following_repo(repo_name, user_id):
367 from rhodecode.model.scm import ScmModel
370 from rhodecode.model.scm import ScmModel
368 return ScmModel().is_following_repo(repo_name, user_id)
371 return ScmModel().is_following_repo(repo_name, user_id)
369
372
370 flash = _Flash()
373 flash = _Flash()
371
374
372
375
373 #==============================================================================
376 #==============================================================================
374 # MERCURIAL FILTERS available via h.
377 # MERCURIAL FILTERS available via h.
375 #==============================================================================
378 #==============================================================================
376 from mercurial import util
379 from mercurial import util
377 from mercurial.templatefilters import person as _person
380 from mercurial.templatefilters import person as _person
378
381
379 def _age(curdate):
382 def _age(curdate):
380 """turns a datetime into an age string."""
383 """turns a datetime into an age string."""
381
384
382 if not curdate:
385 if not curdate:
383 return ''
386 return ''
384
387
385 agescales = [("year", 3600 * 24 * 365),
388 agescales = [("year", 3600 * 24 * 365),
386 ("month", 3600 * 24 * 30),
389 ("month", 3600 * 24 * 30),
387 ("day", 3600 * 24),
390 ("day", 3600 * 24),
388 ("hour", 3600),
391 ("hour", 3600),
389 ("minute", 60),
392 ("minute", 60),
390 ("second", 1), ]
393 ("second", 1), ]
391
394
392 age = datetime.now() - curdate
395 age = datetime.now() - curdate
393 age_seconds = (age.days * agescales[2][1]) + age.seconds
396 age_seconds = (age.days * agescales[2][1]) + age.seconds
394 pos = 1
397 pos = 1
395 for scale in agescales:
398 for scale in agescales:
396 if scale[1] <= age_seconds:
399 if scale[1] <= age_seconds:
397 if pos == 6:pos = 5
400 if pos == 6:pos = 5
398 return time_ago_in_words(curdate, agescales[pos][0]) + ' ' + _('ago')
401 return time_ago_in_words(curdate, agescales[pos][0]) + ' ' + _('ago')
399 pos += 1
402 pos += 1
400
403
401 return _('just now')
404 return _('just now')
402
405
403 age = lambda x:_age(x)
406 age = lambda x:_age(x)
404 capitalize = lambda x: x.capitalize()
407 capitalize = lambda x: x.capitalize()
405 email = util.email
408 email = util.email
406 email_or_none = lambda x: util.email(x) if util.email(x) != x else None
409 email_or_none = lambda x: util.email(x) if util.email(x) != x else None
407 person = lambda x: _person(x)
410 person = lambda x: _person(x)
408 short_id = lambda x: x[:12]
411 short_id = lambda x: x[:12]
409
412
410
413
411 def bool2icon(value):
414 def bool2icon(value):
412 """Returns True/False values represented as small html image of true/false
415 """Returns True/False values represented as small html image of true/false
413 icons
416 icons
414
417
415 :param value: bool value
418 :param value: bool value
416 """
419 """
417
420
418 if value is True:
421 if value is True:
419 return HTML.tag('img', src=url("/images/icons/accept.png"),
422 return HTML.tag('img', src=url("/images/icons/accept.png"),
420 alt=_('True'))
423 alt=_('True'))
421
424
422 if value is False:
425 if value is False:
423 return HTML.tag('img', src=url("/images/icons/cancel.png"),
426 return HTML.tag('img', src=url("/images/icons/cancel.png"),
424 alt=_('False'))
427 alt=_('False'))
425
428
426 return value
429 return value
427
430
428
431
429 def action_parser(user_log, feed=False):
432 def action_parser(user_log, feed=False):
430 """This helper will action_map the specified string action into translated
433 """This helper will action_map the specified string action into translated
431 fancy names with icons and links
434 fancy names with icons and links
432
435
433 :param user_log: user log instance
436 :param user_log: user log instance
434 :param feed: use output for feeds (no html and fancy icons)
437 :param feed: use output for feeds (no html and fancy icons)
435 """
438 """
436
439
437 action = user_log.action
440 action = user_log.action
438 action_params = ' '
441 action_params = ' '
439
442
440 x = action.split(':')
443 x = action.split(':')
441
444
442 if len(x) > 1:
445 if len(x) > 1:
443 action, action_params = x
446 action, action_params = x
444
447
445 def get_cs_links():
448 def get_cs_links():
446 revs_limit = 5 #display this amount always
449 revs_limit = 5 #display this amount always
447 revs_top_limit = 50 #show upto this amount of changesets hidden
450 revs_top_limit = 50 #show upto this amount of changesets hidden
448 revs = action_params.split(',')
451 revs = action_params.split(',')
449 repo_name = user_log.repository.repo_name
452 repo_name = user_log.repository.repo_name
450
453
451 from rhodecode.model.scm import ScmModel
454 from rhodecode.model.scm import ScmModel
452 repo, dbrepo = ScmModel().get(repo_name, retval='repo',
455 repo, dbrepo = ScmModel().get(repo_name, retval='repo',
453 invalidation_list=[])
456 invalidation_list=[])
454
457
455 message = lambda rev: get_changeset_safe(repo, rev).message
458 message = lambda rev: get_changeset_safe(repo, rev).message
456
459
457 cs_links = " " + ', '.join ([link_to(rev,
460 cs_links = " " + ', '.join ([link_to(rev,
458 url('changeset_home',
461 url('changeset_home',
459 repo_name=repo_name,
462 repo_name=repo_name,
460 revision=rev), title=tooltip(message(rev)),
463 revision=rev), title=tooltip(message(rev)),
461 class_='tooltip') for rev in revs[:revs_limit] ])
464 class_='tooltip') for rev in revs[:revs_limit] ])
462
465
463 compare_view = (' <div class="compare_view tooltip" title="%s">'
466 compare_view = (' <div class="compare_view tooltip" title="%s">'
464 '<a href="%s">%s</a> '
467 '<a href="%s">%s</a> '
465 '</div>' % (_('Show all combined changesets %s->%s' \
468 '</div>' % (_('Show all combined changesets %s->%s' \
466 % (revs[0], revs[-1])),
469 % (revs[0], revs[-1])),
467 url('changeset_home', repo_name=repo_name,
470 url('changeset_home', repo_name=repo_name,
468 revision='%s...%s' % (revs[0], revs[-1])
471 revision='%s...%s' % (revs[0], revs[-1])
469 ),
472 ),
470 _('compare view'))
473 _('compare view'))
471 )
474 )
472
475
473 if len(revs) > revs_limit:
476 if len(revs) > revs_limit:
474 uniq_id = revs[0]
477 uniq_id = revs[0]
475 html_tmpl = ('<span> %s '
478 html_tmpl = ('<span> %s '
476 '<a class="show_more" id="_%s" href="#more">%s</a> '
479 '<a class="show_more" id="_%s" href="#more">%s</a> '
477 '%s</span>')
480 '%s</span>')
478 if not feed:
481 if not feed:
479 cs_links += html_tmpl % (_('and'), uniq_id, _('%s more') \
482 cs_links += html_tmpl % (_('and'), uniq_id, _('%s more') \
480 % (len(revs) - revs_limit),
483 % (len(revs) - revs_limit),
481 _('revisions'))
484 _('revisions'))
482
485
483 if not feed:
486 if not feed:
484 html_tmpl = '<span id="%s" style="display:none"> %s </span>'
487 html_tmpl = '<span id="%s" style="display:none"> %s </span>'
485 else:
488 else:
486 html_tmpl = '<span id="%s"> %s </span>'
489 html_tmpl = '<span id="%s"> %s </span>'
487
490
488 cs_links += html_tmpl % (uniq_id, ', '.join([link_to(rev,
491 cs_links += html_tmpl % (uniq_id, ', '.join([link_to(rev,
489 url('changeset_home',
492 url('changeset_home',
490 repo_name=repo_name, revision=rev),
493 repo_name=repo_name, revision=rev),
491 title=message(rev), class_='tooltip')
494 title=message(rev), class_='tooltip')
492 for rev in revs[revs_limit:revs_top_limit]]))
495 for rev in revs[revs_limit:revs_top_limit]]))
493 if len(revs) > 1:
496 if len(revs) > 1:
494 cs_links += compare_view
497 cs_links += compare_view
495 return cs_links
498 return cs_links
496
499
497 def get_fork_name():
500 def get_fork_name():
498 repo_name = action_params
501 repo_name = action_params
499 return _('fork name ') + str(link_to(action_params, url('summary_home',
502 return _('fork name ') + str(link_to(action_params, url('summary_home',
500 repo_name=repo_name,)))
503 repo_name=repo_name,)))
501
504
502 action_map = {'user_deleted_repo':(_('[deleted] repository'), None),
505 action_map = {'user_deleted_repo':(_('[deleted] repository'), None),
503 'user_created_repo':(_('[created] repository'), None),
506 'user_created_repo':(_('[created] repository'), None),
504 'user_forked_repo':(_('[forked] repository'), get_fork_name),
507 'user_forked_repo':(_('[forked] repository'), get_fork_name),
505 'user_updated_repo':(_('[updated] repository'), None),
508 'user_updated_repo':(_('[updated] repository'), None),
506 'admin_deleted_repo':(_('[delete] repository'), None),
509 'admin_deleted_repo':(_('[delete] repository'), None),
507 'admin_created_repo':(_('[created] repository'), None),
510 'admin_created_repo':(_('[created] repository'), None),
508 'admin_forked_repo':(_('[forked] repository'), None),
511 'admin_forked_repo':(_('[forked] repository'), None),
509 'admin_updated_repo':(_('[updated] repository'), None),
512 'admin_updated_repo':(_('[updated] repository'), None),
510 'push':(_('[pushed] into'), get_cs_links),
513 'push':(_('[pushed] into'), get_cs_links),
511 'push_remote':(_('[pulled from remote] into'), get_cs_links),
514 'push_remote':(_('[pulled from remote] into'), get_cs_links),
512 'pull':(_('[pulled] from'), None),
515 'pull':(_('[pulled] from'), None),
513 'started_following_repo':(_('[started following] repository'), None),
516 'started_following_repo':(_('[started following] repository'), None),
514 'stopped_following_repo':(_('[stopped following] repository'), None),
517 'stopped_following_repo':(_('[stopped following] repository'), None),
515 }
518 }
516
519
517 action_str = action_map.get(action, action)
520 action_str = action_map.get(action, action)
518 if feed:
521 if feed:
519 action = action_str[0].replace('[', '').replace(']', '')
522 action = action_str[0].replace('[', '').replace(']', '')
520 else:
523 else:
521 action = action_str[0].replace('[', '<span class="journal_highlight">')\
524 action = action_str[0].replace('[', '<span class="journal_highlight">')\
522 .replace(']', '</span>')
525 .replace(']', '</span>')
523
526
524 action_params_func = lambda :""
527 action_params_func = lambda :""
525
528
526 if callable(action_str[1]):
529 if callable(action_str[1]):
527 action_params_func = action_str[1]
530 action_params_func = action_str[1]
528
531
529 return [literal(action), action_params_func]
532 return [literal(action), action_params_func]
530
533
531 def action_parser_icon(user_log):
534 def action_parser_icon(user_log):
532 action = user_log.action
535 action = user_log.action
533 action_params = None
536 action_params = None
534 x = action.split(':')
537 x = action.split(':')
535
538
536 if len(x) > 1:
539 if len(x) > 1:
537 action, action_params = x
540 action, action_params = x
538
541
539 tmpl = """<img src="%s%s" alt="%s"/>"""
542 tmpl = """<img src="%s%s" alt="%s"/>"""
540 map = {'user_deleted_repo':'database_delete.png',
543 map = {'user_deleted_repo':'database_delete.png',
541 'user_created_repo':'database_add.png',
544 'user_created_repo':'database_add.png',
542 'user_forked_repo':'arrow_divide.png',
545 'user_forked_repo':'arrow_divide.png',
543 'user_updated_repo':'database_edit.png',
546 'user_updated_repo':'database_edit.png',
544 'admin_deleted_repo':'database_delete.png',
547 'admin_deleted_repo':'database_delete.png',
545 'admin_created_repo':'database_add.png',
548 'admin_created_repo':'database_add.png',
546 'admin_forked_repo':'arrow_divide.png',
549 'admin_forked_repo':'arrow_divide.png',
547 'admin_updated_repo':'database_edit.png',
550 'admin_updated_repo':'database_edit.png',
548 'push':'script_add.png',
551 'push':'script_add.png',
549 'push_remote':'connect.png',
552 'push_remote':'connect.png',
550 'pull':'down_16.png',
553 'pull':'down_16.png',
551 'started_following_repo':'heart_add.png',
554 'started_following_repo':'heart_add.png',
552 'stopped_following_repo':'heart_delete.png',
555 'stopped_following_repo':'heart_delete.png',
553 }
556 }
554 return literal(tmpl % ((url('/images/icons/')),
557 return literal(tmpl % ((url('/images/icons/')),
555 map.get(action, action), action))
558 map.get(action, action), action))
556
559
557
560
558 #==============================================================================
561 #==============================================================================
559 # PERMS
562 # PERMS
560 #==============================================================================
563 #==============================================================================
561 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
564 from rhodecode.lib.auth import HasPermissionAny, HasPermissionAll, \
562 HasRepoPermissionAny, HasRepoPermissionAll
565 HasRepoPermissionAny, HasRepoPermissionAll
563
566
564 #==============================================================================
567 #==============================================================================
565 # GRAVATAR URL
568 # GRAVATAR URL
566 #==============================================================================
569 #==============================================================================
567
570
568 def gravatar_url(email_address, size=30):
571 def gravatar_url(email_address, size=30):
569 if not str2bool(config['app_conf'].get('use_gravatar')):
572 if not str2bool(config['app_conf'].get('use_gravatar')):
570 return "/images/user%s.png" % size
573 return "/images/user%s.png" % size
571
574
572 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
575 ssl_enabled = 'https' == request.environ.get('wsgi.url_scheme')
573 default = 'identicon'
576 default = 'identicon'
574 baseurl_nossl = "http://www.gravatar.com/avatar/"
577 baseurl_nossl = "http://www.gravatar.com/avatar/"
575 baseurl_ssl = "https://secure.gravatar.com/avatar/"
578 baseurl_ssl = "https://secure.gravatar.com/avatar/"
576 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
579 baseurl = baseurl_ssl if ssl_enabled else baseurl_nossl
577
580
578 if isinstance(email_address, unicode):
581 if isinstance(email_address, unicode):
579 #hashlib crashes on unicode items
582 #hashlib crashes on unicode items
580 email_address = email_address.encode('utf8', 'replace')
583 email_address = email_address.encode('utf8', 'replace')
581 # construct the url
584 # construct the url
582 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
585 gravatar_url = baseurl + hashlib.md5(email_address.lower()).hexdigest() + "?"
583 gravatar_url += urllib.urlencode({'d':default, 's':str(size)})
586 gravatar_url += urllib.urlencode({'d':default, 's':str(size)})
584
587
585 return gravatar_url
588 return gravatar_url
586
589
587
590
588 #==============================================================================
591 #==============================================================================
589 # REPO PAGER
592 # REPO PAGER
590 #==============================================================================
593 #==============================================================================
591 class RepoPage(Page):
594 class RepoPage(Page):
592
595
593 def __init__(self, collection, page=1, items_per_page=20,
596 def __init__(self, collection, page=1, items_per_page=20,
594 item_count=None, url=None, branch_name=None, **kwargs):
597 item_count=None, url=None, branch_name=None, **kwargs):
595
598
596 """Create a "RepoPage" instance. special pager for paging
599 """Create a "RepoPage" instance. special pager for paging
597 repository
600 repository
598 """
601 """
599 self._url_generator = url
602 self._url_generator = url
600
603
601 # Safe the kwargs class-wide so they can be used in the pager() method
604 # Safe the kwargs class-wide so they can be used in the pager() method
602 self.kwargs = kwargs
605 self.kwargs = kwargs
603
606
604 # Save a reference to the collection
607 # Save a reference to the collection
605 self.original_collection = collection
608 self.original_collection = collection
606
609
607 self.collection = collection
610 self.collection = collection
608
611
609 # The self.page is the number of the current page.
612 # The self.page is the number of the current page.
610 # The first page has the number 1!
613 # The first page has the number 1!
611 try:
614 try:
612 self.page = int(page) # make it int() if we get it as a string
615 self.page = int(page) # make it int() if we get it as a string
613 except (ValueError, TypeError):
616 except (ValueError, TypeError):
614 self.page = 1
617 self.page = 1
615
618
616 self.items_per_page = items_per_page
619 self.items_per_page = items_per_page
617
620
618 # Unless the user tells us how many items the collections has
621 # Unless the user tells us how many items the collections has
619 # we calculate that ourselves.
622 # we calculate that ourselves.
620 if item_count is not None:
623 if item_count is not None:
621 self.item_count = item_count
624 self.item_count = item_count
622 else:
625 else:
623 self.item_count = len(self.collection)
626 self.item_count = len(self.collection)
624
627
625 # Compute the number of the first and last available page
628 # Compute the number of the first and last available page
626 if self.item_count > 0:
629 if self.item_count > 0:
627 self.first_page = 1
630 self.first_page = 1
628 self.page_count = ((self.item_count - 1) / self.items_per_page) + 1
631 self.page_count = ((self.item_count - 1) / self.items_per_page) + 1
629 self.last_page = self.first_page + self.page_count - 1
632 self.last_page = self.first_page + self.page_count - 1
630
633
631 # Make sure that the requested page number is the range of valid pages
634 # Make sure that the requested page number is the range of valid pages
632 if self.page > self.last_page:
635 if self.page > self.last_page:
633 self.page = self.last_page
636 self.page = self.last_page
634 elif self.page < self.first_page:
637 elif self.page < self.first_page:
635 self.page = self.first_page
638 self.page = self.first_page
636
639
637 # Note: the number of items on this page can be less than
640 # Note: the number of items on this page can be less than
638 # items_per_page if the last page is not full
641 # items_per_page if the last page is not full
639 self.first_item = max(0, (self.item_count) - (self.page * items_per_page))
642 self.first_item = max(0, (self.item_count) - (self.page * items_per_page))
640 self.last_item = ((self.item_count - 1) - items_per_page * (self.page - 1))
643 self.last_item = ((self.item_count - 1) - items_per_page * (self.page - 1))
641
644
642 iterator = self.collection.get_changesets(start=self.first_item,
645 iterator = self.collection.get_changesets(start=self.first_item,
643 end=self.last_item,
646 end=self.last_item,
644 reverse=True,
647 reverse=True,
645 branch_name=branch_name)
648 branch_name=branch_name)
646 self.items = list(iterator)
649 self.items = list(iterator)
647
650
648 # Links to previous and next page
651 # Links to previous and next page
649 if self.page > self.first_page:
652 if self.page > self.first_page:
650 self.previous_page = self.page - 1
653 self.previous_page = self.page - 1
651 else:
654 else:
652 self.previous_page = None
655 self.previous_page = None
653
656
654 if self.page < self.last_page:
657 if self.page < self.last_page:
655 self.next_page = self.page + 1
658 self.next_page = self.page + 1
656 else:
659 else:
657 self.next_page = None
660 self.next_page = None
658
661
659 # No items available
662 # No items available
660 else:
663 else:
661 self.first_page = None
664 self.first_page = None
662 self.page_count = 0
665 self.page_count = 0
663 self.last_page = None
666 self.last_page = None
664 self.first_item = None
667 self.first_item = None
665 self.last_item = None
668 self.last_item = None
666 self.previous_page = None
669 self.previous_page = None
667 self.next_page = None
670 self.next_page = None
668 self.items = []
671 self.items = []
669
672
670 # This is a subclass of the 'list' type. Initialise the list now.
673 # This is a subclass of the 'list' type. Initialise the list now.
671 list.__init__(self, self.items)
674 list.__init__(self, self.items)
672
675
673
676
674 def changed_tooltip(nodes):
677 def changed_tooltip(nodes):
675 if nodes:
678 if nodes:
676 pref = ': <br/> '
679 pref = ': <br/> '
677 suf = ''
680 suf = ''
678 if len(nodes) > 30:
681 if len(nodes) > 30:
679 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
682 suf = '<br/>' + _(' and %s more') % (len(nodes) - 30)
680 return literal(pref + '<br/> '.join([x.path.decode('utf-8', 'replace') for x in nodes[:30]]) + suf)
683 return literal(pref + '<br/> '.join([x.path.decode('utf-8', 'replace') for x in nodes[:30]]) + suf)
681 else:
684 else:
682 return ': ' + _('No Files')
685 return ': ' + _('No Files')
683
686
684
687
685
688
686 def repo_link(groups_and_repos):
689 def repo_link(groups_and_repos):
687 groups, repo_name = groups_and_repos
690 groups, repo_name = groups_and_repos
688
691
689 if not groups:
692 if not groups:
690 return repo_name
693 return repo_name
691 else:
694 else:
692 def make_link(group):
695 def make_link(group):
693 return link_to(group.group_name, url('/', group.group_id))
696 return link_to(group.group_name, url('repos_group', id=group.group_id))
694 return literal(' &raquo; '.join(map(make_link, groups)) + \
697 return literal(' &raquo; '.join(map(make_link, groups)) + \
695 " &raquo; " + repo_name)
698 " &raquo; " + repo_name)
696
699
697
700
@@ -1,241 +1,241 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.indexers.daemon
3 rhodecode.lib.indexers.daemon
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 A deamon will read from task table and run tasks
6 A deamon will read from task table and run tasks
7
7
8 :created_on: Jan 26, 2010
8 :created_on: Jan 26, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2009-2011 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software; you can redistribute it and/or
13 # This program is free software; you can redistribute it and/or
14 # modify it under the terms of the GNU General Public License
14 # modify it under the terms of the GNU General Public License
15 # as published by the Free Software Foundation; version 2
15 # as published by the Free Software Foundation; version 2
16 # of the License or (at your opinion) any later version of the license.
16 # of the License or (at your opinion) any later version of the license.
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, write to the Free Software
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
26 # MA 02110-1301, USA.
26 # MA 02110-1301, USA.
27
27
28 import os
28 import os
29 import sys
29 import sys
30 import logging
30 import logging
31 import traceback
31 import traceback
32
32
33 from shutil import rmtree
33 from shutil import rmtree
34 from time import mktime
34 from time import mktime
35
35
36 from os.path import dirname as dn
36 from os.path import dirname as dn
37 from os.path import join as jn
37 from os.path import join as jn
38
38
39 #to get the rhodecode import
39 #to get the rhodecode import
40 project_path = dn(dn(dn(dn(os.path.realpath(__file__)))))
40 project_path = dn(dn(dn(dn(os.path.realpath(__file__)))))
41 sys.path.append(project_path)
41 sys.path.append(project_path)
42
42
43
43
44 from rhodecode.model.scm import ScmModel
44 from rhodecode.model.scm import ScmModel
45 from rhodecode.lib import safe_unicode
45 from rhodecode.lib import safe_unicode
46 from rhodecode.lib.indexers import INDEX_EXTENSIONS, SCHEMA, IDX_NAME
46 from rhodecode.lib.indexers import INDEX_EXTENSIONS, SCHEMA, IDX_NAME
47
47
48 from vcs.exceptions import ChangesetError, RepositoryError
48 from vcs.exceptions import ChangesetError, RepositoryError
49
49
50 from whoosh.index import create_in, open_dir
50 from whoosh.index import create_in, open_dir
51
51
52
52
53
53
54 log = logging.getLogger('whooshIndexer')
54 log = logging.getLogger('whooshIndexer')
55 # create logger
55 # create logger
56 log.setLevel(logging.DEBUG)
56 log.setLevel(logging.DEBUG)
57 log.propagate = False
57 log.propagate = False
58 # create console handler and set level to debug
58 # create console handler and set level to debug
59 ch = logging.StreamHandler()
59 ch = logging.StreamHandler()
60 ch.setLevel(logging.DEBUG)
60 ch.setLevel(logging.DEBUG)
61
61
62 # create formatter
62 # create formatter
63 formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
63 formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s")
64
64
65 # add formatter to ch
65 # add formatter to ch
66 ch.setFormatter(formatter)
66 ch.setFormatter(formatter)
67
67
68 # add ch to logger
68 # add ch to logger
69 log.addHandler(ch)
69 log.addHandler(ch)
70
70
71 class WhooshIndexingDaemon(object):
71 class WhooshIndexingDaemon(object):
72 """
72 """
73 Deamon for atomic jobs
73 Deamon for atomic jobs
74 """
74 """
75
75
76 def __init__(self, indexname='HG_INDEX', index_location=None,
76 def __init__(self, indexname='HG_INDEX', index_location=None,
77 repo_location=None, sa=None, repo_list=None):
77 repo_location=None, sa=None, repo_list=None):
78 self.indexname = indexname
78 self.indexname = indexname
79
79
80 self.index_location = index_location
80 self.index_location = index_location
81 if not index_location:
81 if not index_location:
82 raise Exception('You have to provide index location')
82 raise Exception('You have to provide index location')
83
83
84 self.repo_location = repo_location
84 self.repo_location = repo_location
85 if not repo_location:
85 if not repo_location:
86 raise Exception('You have to provide repositories location')
86 raise Exception('You have to provide repositories location')
87
87
88 self.repo_paths = ScmModel(sa).repo_scan(self.repo_location)
88 self.repo_paths = ScmModel(sa).repo_scan(self.repo_location)
89
89
90 if repo_list:
90 if repo_list:
91 filtered_repo_paths = {}
91 filtered_repo_paths = {}
92 for repo_name, repo in self.repo_paths.items():
92 for repo_name, repo in self.repo_paths.items():
93 if repo_name in repo_list:
93 if repo_name in repo_list:
94 filtered_repo_paths[repo.name] = repo
94 filtered_repo_paths[repo_name] = repo
95
95
96 self.repo_paths = filtered_repo_paths
96 self.repo_paths = filtered_repo_paths
97
97
98
98
99 self.initial = False
99 self.initial = False
100 if not os.path.isdir(self.index_location):
100 if not os.path.isdir(self.index_location):
101 os.makedirs(self.index_location)
101 os.makedirs(self.index_location)
102 log.info('Cannot run incremental index since it does not'
102 log.info('Cannot run incremental index since it does not'
103 ' yet exist running full build')
103 ' yet exist running full build')
104 self.initial = True
104 self.initial = True
105
105
106 def get_paths(self, repo):
106 def get_paths(self, repo):
107 """recursive walk in root dir and return a set of all path in that dir
107 """recursive walk in root dir and return a set of all path in that dir
108 based on repository walk function
108 based on repository walk function
109 """
109 """
110 index_paths_ = set()
110 index_paths_ = set()
111 try:
111 try:
112 tip = repo.get_changeset('tip')
112 tip = repo.get_changeset('tip')
113 for topnode, dirs, files in tip.walk('/'):
113 for topnode, dirs, files in tip.walk('/'):
114 for f in files:
114 for f in files:
115 index_paths_.add(jn(repo.path, f.path))
115 index_paths_.add(jn(repo.path, f.path))
116 for dir in dirs:
116 for dir in dirs:
117 for f in files:
117 for f in files:
118 index_paths_.add(jn(repo.path, f.path))
118 index_paths_.add(jn(repo.path, f.path))
119
119
120 except RepositoryError, e:
120 except RepositoryError, e:
121 log.debug(traceback.format_exc())
121 log.debug(traceback.format_exc())
122 pass
122 pass
123 return index_paths_
123 return index_paths_
124
124
125 def get_node(self, repo, path):
125 def get_node(self, repo, path):
126 n_path = path[len(repo.path) + 1:]
126 n_path = path[len(repo.path) + 1:]
127 node = repo.get_changeset().get_node(n_path)
127 node = repo.get_changeset().get_node(n_path)
128 return node
128 return node
129
129
130 def get_node_mtime(self, node):
130 def get_node_mtime(self, node):
131 return mktime(node.last_changeset.date.timetuple())
131 return mktime(node.last_changeset.date.timetuple())
132
132
133 def add_doc(self, writer, path, repo):
133 def add_doc(self, writer, path, repo, repo_name):
134 """Adding doc to writer this function itself fetches data from
134 """Adding doc to writer this function itself fetches data from
135 the instance of vcs backend"""
135 the instance of vcs backend"""
136 node = self.get_node(repo, path)
136 node = self.get_node(repo, path)
137
137
138 #we just index the content of chosen files, and skip binary files
138 #we just index the content of chosen files, and skip binary files
139 if node.extension in INDEX_EXTENSIONS and not node.is_binary:
139 if node.extension in INDEX_EXTENSIONS and not node.is_binary:
140
140
141 u_content = node.content
141 u_content = node.content
142 if not isinstance(u_content, unicode):
142 if not isinstance(u_content, unicode):
143 log.warning(' >> %s Could not get this content as unicode '
143 log.warning(' >> %s Could not get this content as unicode '
144 'replacing with empty content', path)
144 'replacing with empty content', path)
145 u_content = u''
145 u_content = u''
146 else:
146 else:
147 log.debug(' >> %s [WITH CONTENT]' % path)
147 log.debug(' >> %s [WITH CONTENT]' % path)
148
148
149 else:
149 else:
150 log.debug(' >> %s' % path)
150 log.debug(' >> %s' % path)
151 #just index file name without it's content
151 #just index file name without it's content
152 u_content = u''
152 u_content = u''
153
153
154 writer.add_document(owner=unicode(repo.contact),
154 writer.add_document(owner=unicode(repo.contact),
155 repository=safe_unicode(repo.name),
155 repository=safe_unicode(repo_name),
156 path=safe_unicode(path),
156 path=safe_unicode(path),
157 content=u_content,
157 content=u_content,
158 modtime=self.get_node_mtime(node),
158 modtime=self.get_node_mtime(node),
159 extension=node.extension)
159 extension=node.extension)
160
160
161
161
162 def build_index(self):
162 def build_index(self):
163 if os.path.exists(self.index_location):
163 if os.path.exists(self.index_location):
164 log.debug('removing previous index')
164 log.debug('removing previous index')
165 rmtree(self.index_location)
165 rmtree(self.index_location)
166
166
167 if not os.path.exists(self.index_location):
167 if not os.path.exists(self.index_location):
168 os.mkdir(self.index_location)
168 os.mkdir(self.index_location)
169
169
170 idx = create_in(self.index_location, SCHEMA, indexname=IDX_NAME)
170 idx = create_in(self.index_location, SCHEMA, indexname=IDX_NAME)
171 writer = idx.writer()
171 writer = idx.writer()
172
172
173 for repo in self.repo_paths.values():
173 for repo_name, repo in self.repo_paths.items():
174 log.debug('building index @ %s' % repo.path)
174 log.debug('building index @ %s' % repo.path)
175
175
176 for idx_path in self.get_paths(repo):
176 for idx_path in self.get_paths(repo):
177 self.add_doc(writer, idx_path, repo)
177 self.add_doc(writer, idx_path, repo, repo_name)
178
178
179 log.debug('>> COMMITING CHANGES <<')
179 log.debug('>> COMMITING CHANGES <<')
180 writer.commit(merge=True)
180 writer.commit(merge=True)
181 log.debug('>>> FINISHED BUILDING INDEX <<<')
181 log.debug('>>> FINISHED BUILDING INDEX <<<')
182
182
183
183
184 def update_index(self):
184 def update_index(self):
185 log.debug('STARTING INCREMENTAL INDEXING UPDATE')
185 log.debug('STARTING INCREMENTAL INDEXING UPDATE')
186
186
187 idx = open_dir(self.index_location, indexname=self.indexname)
187 idx = open_dir(self.index_location, indexname=self.indexname)
188 # The set of all paths in the index
188 # The set of all paths in the index
189 indexed_paths = set()
189 indexed_paths = set()
190 # The set of all paths we need to re-index
190 # The set of all paths we need to re-index
191 to_index = set()
191 to_index = set()
192
192
193 reader = idx.reader()
193 reader = idx.reader()
194 writer = idx.writer()
194 writer = idx.writer()
195
195
196 # Loop over the stored fields in the index
196 # Loop over the stored fields in the index
197 for fields in reader.all_stored_fields():
197 for fields in reader.all_stored_fields():
198 indexed_path = fields['path']
198 indexed_path = fields['path']
199 indexed_paths.add(indexed_path)
199 indexed_paths.add(indexed_path)
200
200
201 repo = self.repo_paths[fields['repository']]
201 repo = self.repo_paths[fields['repository']]
202
202
203 try:
203 try:
204 node = self.get_node(repo, indexed_path)
204 node = self.get_node(repo, indexed_path)
205 except ChangesetError:
205 except ChangesetError:
206 # This file was deleted since it was indexed
206 # This file was deleted since it was indexed
207 log.debug('removing from index %s' % indexed_path)
207 log.debug('removing from index %s' % indexed_path)
208 writer.delete_by_term('path', indexed_path)
208 writer.delete_by_term('path', indexed_path)
209
209
210 else:
210 else:
211 # Check if this file was changed since it was indexed
211 # Check if this file was changed since it was indexed
212 indexed_time = fields['modtime']
212 indexed_time = fields['modtime']
213 mtime = self.get_node_mtime(node)
213 mtime = self.get_node_mtime(node)
214 if mtime > indexed_time:
214 if mtime > indexed_time:
215 # The file has changed, delete it and add it to the list of
215 # The file has changed, delete it and add it to the list of
216 # files to reindex
216 # files to reindex
217 log.debug('adding to reindex list %s' % indexed_path)
217 log.debug('adding to reindex list %s' % indexed_path)
218 writer.delete_by_term('path', indexed_path)
218 writer.delete_by_term('path', indexed_path)
219 to_index.add(indexed_path)
219 to_index.add(indexed_path)
220
220
221 # Loop over the files in the filesystem
221 # Loop over the files in the filesystem
222 # Assume we have a function that gathers the filenames of the
222 # Assume we have a function that gathers the filenames of the
223 # documents to be indexed
223 # documents to be indexed
224 for repo in self.repo_paths.values():
224 for repo_name, repo in self.repo_paths.items():
225 for path in self.get_paths(repo):
225 for path in self.get_paths(repo):
226 if path in to_index or path not in indexed_paths:
226 if path in to_index or path not in indexed_paths:
227 # This is either a file that's changed, or a new file
227 # This is either a file that's changed, or a new file
228 # that wasn't indexed before. So index it!
228 # that wasn't indexed before. So index it!
229 self.add_doc(writer, path, repo)
229 self.add_doc(writer, path, repo, repo_name)
230 log.debug('re indexing %s' % path)
230 log.debug('re indexing %s' % path)
231
231
232 log.debug('>> COMMITING CHANGES <<')
232 log.debug('>> COMMITING CHANGES <<')
233 writer.commit(merge=True)
233 writer.commit(merge=True)
234 log.debug('>>> FINISHED REBUILDING INDEX <<<')
234 log.debug('>>> FINISHED REBUILDING INDEX <<<')
235
235
236 def run(self, full_index=False):
236 def run(self, full_index=False):
237 """Run daemon"""
237 """Run daemon"""
238 if full_index or self.initial:
238 if full_index or self.initial:
239 self.build_index()
239 self.build_index()
240 else:
240 else:
241 self.update_index()
241 self.update_index()
@@ -1,211 +1,211 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 <%inherit file="/base/base.html"/>
2 <%inherit file="/base/base.html"/>
3
3
4 <%def name="title()">
4 <%def name="title()">
5 ${_('My account')} ${c.rhodecode_user.username} - ${c.rhodecode_name}
5 ${_('My account')} ${c.rhodecode_user.username} - ${c.rhodecode_name}
6 </%def>
6 </%def>
7
7
8 <%def name="breadcrumbs_links()">
8 <%def name="breadcrumbs_links()">
9 ${_('My Account')}
9 ${_('My Account')}
10 </%def>
10 </%def>
11
11
12 <%def name="page_nav()">
12 <%def name="page_nav()">
13 ${self.menu('admin')}
13 ${self.menu('admin')}
14 </%def>
14 </%def>
15
15
16 <%def name="main()">
16 <%def name="main()">
17
17
18 <div class="box box-left">
18 <div class="box box-left">
19 <!-- box / title -->
19 <!-- box / title -->
20 <div class="title">
20 <div class="title">
21 ${self.breadcrumbs()}
21 ${self.breadcrumbs()}
22 </div>
22 </div>
23 <!-- end box / title -->
23 <!-- end box / title -->
24 <div>
24 <div>
25 ${h.form(url('admin_settings_my_account_update'),method='put')}
25 ${h.form(url('admin_settings_my_account_update'),method='put')}
26 <div class="form">
26 <div class="form">
27
27
28 <div class="field">
28 <div class="field">
29 <div class="gravatar_box">
29 <div class="gravatar_box">
30 <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(c.user.email)}"/></div>
30 <div class="gravatar"><img alt="gravatar" src="${h.gravatar_url(c.user.email)}"/></div>
31 <p>
31 <p>
32 <strong>Change your avatar at <a href="http://gravatar.com">gravatar.com</a></strong><br/>
32 <strong>Change your avatar at <a href="http://gravatar.com">gravatar.com</a></strong><br/>
33 ${_('Using')} ${c.user.email}
33 ${_('Using')} ${c.user.email}
34 </p>
34 </p>
35 </div>
35 </div>
36 </div>
36 </div>
37 <div class="field">
37 <div class="field">
38 <div class="label">
38 <div class="label">
39 <label>${_('API key')}</label> ${c.user.api_key}
39 <label>${_('API key')}</label> ${c.user.api_key}
40 </div>
40 </div>
41 </div>
41 </div>
42 <div class="fields">
42 <div class="fields">
43 <div class="field">
43 <div class="field">
44 <div class="label">
44 <div class="label">
45 <label for="username">${_('Username')}:</label>
45 <label for="username">${_('Username')}:</label>
46 </div>
46 </div>
47 <div class="input">
47 <div class="input">
48 ${h.text('username',class_="medium")}
48 ${h.text('username',class_="medium")}
49 </div>
49 </div>
50 </div>
50 </div>
51
51
52 <div class="field">
52 <div class="field">
53 <div class="label">
53 <div class="label">
54 <label for="new_password">${_('New password')}:</label>
54 <label for="new_password">${_('New password')}:</label>
55 </div>
55 </div>
56 <div class="input">
56 <div class="input">
57 ${h.password('new_password',class_="medium")}
57 ${h.password('new_password',class_="medium")}
58 </div>
58 </div>
59 </div>
59 </div>
60
60
61 <div class="field">
61 <div class="field">
62 <div class="label">
62 <div class="label">
63 <label for="name">${_('First Name')}:</label>
63 <label for="name">${_('First Name')}:</label>
64 </div>
64 </div>
65 <div class="input">
65 <div class="input">
66 ${h.text('name',class_="medium")}
66 ${h.text('name',class_="medium")}
67 </div>
67 </div>
68 </div>
68 </div>
69
69
70 <div class="field">
70 <div class="field">
71 <div class="label">
71 <div class="label">
72 <label for="lastname">${_('Last Name')}:</label>
72 <label for="lastname">${_('Last Name')}:</label>
73 </div>
73 </div>
74 <div class="input">
74 <div class="input">
75 ${h.text('lastname',class_="medium")}
75 ${h.text('lastname',class_="medium")}
76 </div>
76 </div>
77 </div>
77 </div>
78
78
79 <div class="field">
79 <div class="field">
80 <div class="label">
80 <div class="label">
81 <label for="email">${_('Email')}:</label>
81 <label for="email">${_('Email')}:</label>
82 </div>
82 </div>
83 <div class="input">
83 <div class="input">
84 ${h.text('email',class_="medium")}
84 ${h.text('email',class_="medium")}
85 </div>
85 </div>
86 </div>
86 </div>
87
87
88 <div class="buttons">
88 <div class="buttons">
89 ${h.submit('save','Save',class_="ui-button")}
89 ${h.submit('save','Save',class_="ui-button")}
90 ${h.reset('reset','Reset',class_="ui-button")}
90 ${h.reset('reset','Reset',class_="ui-button")}
91 </div>
91 </div>
92 </div>
92 </div>
93 </div>
93 </div>
94 ${h.end_form()}
94 ${h.end_form()}
95 </div>
95 </div>
96 </div>
96 </div>
97
97
98 <div class="box box-right">
98 <div class="box box-right">
99 <!-- box / title -->
99 <!-- box / title -->
100 <div class="title">
100 <div class="title">
101 <h5>${_('My repositories')}
101 <h5>${_('My repositories')}
102 <input class="top-right-rounded-corner top-left-rounded-corner bottom-left-rounded-corner bottom-right-rounded-corner" id="q_filter" size="15" type="text" name="filter" value="${_('quick filter...')}"/>
102 <input class="top-right-rounded-corner top-left-rounded-corner bottom-left-rounded-corner bottom-right-rounded-corner" id="q_filter" size="15" type="text" name="filter" value="${_('quick filter...')}"/>
103 </h5>
103 </h5>
104 %if h.HasPermissionAny('hg.admin','hg.create.repository')():
104 %if h.HasPermissionAny('hg.admin','hg.create.repository')():
105 <ul class="links">
105 <ul class="links">
106 <li>
106 <li>
107 <span>${h.link_to(_('ADD REPOSITORY'),h.url('admin_settings_create_repository'))}</span>
107 <span>${h.link_to(_('ADD REPOSITORY'),h.url('admin_settings_create_repository'))}</span>
108 </li>
108 </li>
109 </ul>
109 </ul>
110 %endif
110 %endif
111 </div>
111 </div>
112 <!-- end box / title -->
112 <!-- end box / title -->
113 <div class="table">
113 <div class="table">
114 <table>
114 <table>
115 <thead>
115 <thead>
116 <tr>
116 <tr>
117 <th class="left">${_('Name')}</th>
117 <th class="left">${_('Name')}</th>
118 <th class="left">${_('revision')}</th>
118 <th class="left">${_('revision')}</th>
119 <th colspan="2" class="left">${_('action')}</th>
119 <th colspan="2" class="left">${_('action')}</th>
120 </thead>
120 </thead>
121 <tbody>
121 <tbody>
122 %if c.user_repos:
122 %if c.user_repos:
123 %for repo in c.user_repos:
123 %for repo in c.user_repos:
124 <tr>
124 <tr>
125 <td>
125 <td>
126 %if repo['dbrepo']['repo_type'] =='hg':
126 %if repo['dbrepo']['repo_type'] =='hg':
127 <img class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="${h.url("/images/icons/hgicon.png")}"/>
127 <img class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="${h.url("/images/icons/hgicon.png")}"/>
128 %elif repo['dbrepo']['repo_type'] =='git':
128 %elif repo['dbrepo']['repo_type'] =='git':
129 <img class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="${h.url("/images/icons/giticon.png")}"/>
129 <img class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="${h.url("/images/icons/giticon.png")}"/>
130 %else:
130 %else:
131
131
132 %endif
132 %endif
133 %if repo['dbrepo']['private']:
133 %if repo['dbrepo']['private']:
134 <img class="icon" alt="${_('private')}" src="${h.url("/images/icons/lock.png")}"/>
134 <img class="icon" alt="${_('private')}" src="${h.url("/images/icons/lock.png")}"/>
135 %else:
135 %else:
136 <img class="icon" alt="${_('public')}" src="${h.url("/images/icons/lock_open.png")}"/>
136 <img class="icon" alt="${_('public')}" src="${h.url("/images/icons/lock_open.png")}"/>
137 %endif
137 %endif
138
138
139 ${h.link_to(repo['repo'].name, h.url('summary_home',repo_name=repo['repo'].name),class_="repo_name")}
139 ${h.link_to(repo['name'], h.url('summary_home',repo_name=repo['name']),class_="repo_name")}
140 %if repo['dbrepo_fork']:
140 %if repo['dbrepo_fork']:
141 <a href="${h.url('summary_home',repo_name=repo['dbrepo_fork']['repo_name'])}">
141 <a href="${h.url('summary_home',repo_name=repo['dbrepo_fork']['repo_name'])}">
142 <img class="icon" alt="${_('public')}"
142 <img class="icon" alt="${_('public')}"
143 title="${_('Fork of')} ${repo['dbrepo_fork']['repo_name']}"
143 title="${_('Fork of')} ${repo['dbrepo_fork']['repo_name']}"
144 src="${h.url("/images/icons/arrow_divide.png")}"/></a>
144 src="${h.url("/images/icons/arrow_divide.png")}"/></a>
145 %endif
145 %endif
146 </td>
146 </td>
147 <td><span class="tooltip" title="${repo['repo'].last_change}">${("r%s:%s") % (h.get_changeset_safe(repo['repo'],'tip').revision,h.short_id(h.get_changeset_safe(repo['repo'],'tip').raw_id))}</span></td>
147 <td><span class="tooltip" title="${repo['repo'].last_change}">${("r%s:%s") % (h.get_changeset_safe(repo['repo'],'tip').revision,h.short_id(h.get_changeset_safe(repo['repo'],'tip').raw_id))}</span></td>
148 <td><a href="${h.url('repo_settings_home',repo_name=repo['repo'].name)}" title="${_('edit')}"><img class="icon" alt="${_('private')}" src="${h.url("/images/icons/application_form_edit.png")}"/></a></td>
148 <td><a href="${h.url('repo_settings_home',repo_name=repo['name'])}" title="${_('edit')}"><img class="icon" alt="${_('private')}" src="${h.url("/images/icons/application_form_edit.png")}"/></a></td>
149 <td>
149 <td>
150 ${h.form(url('repo_settings_delete', repo_name=repo['repo'].name),method='delete')}
150 ${h.form(url('repo_settings_delete', repo_name=repo['name']),method='delete')}
151 ${h.submit('remove_%s' % repo['repo'].name,'',class_="delete_icon action_button",onclick="return confirm('Confirm to delete this repository');")}
151 ${h.submit('remove_%s' % repo['name'],'',class_="delete_icon action_button",onclick="return confirm('Confirm to delete this repository');")}
152 ${h.end_form()}
152 ${h.end_form()}
153 </td>
153 </td>
154 </tr>
154 </tr>
155 %endfor
155 %endfor
156 %else:
156 %else:
157 ${_('No repositories yet')}
157 ${_('No repositories yet')}
158 %if h.HasPermissionAny('hg.admin','hg.create.repository')():
158 %if h.HasPermissionAny('hg.admin','hg.create.repository')():
159 ${h.link_to(_('create one now'),h.url('admin_settings_create_repository'))}
159 ${h.link_to(_('create one now'),h.url('admin_settings_create_repository'))}
160 %endif
160 %endif
161 %endif
161 %endif
162 </tbody>
162 </tbody>
163 </table>
163 </table>
164 </div>
164 </div>
165
165
166 </div>
166 </div>
167 <script type="text/javascript">
167 <script type="text/javascript">
168 var D = YAHOO.util.Dom;
168 var D = YAHOO.util.Dom;
169 var E = YAHOO.util.Event;
169 var E = YAHOO.util.Event;
170 var S = YAHOO.util.Selector;
170 var S = YAHOO.util.Selector;
171
171
172 var q_filter = D.get('q_filter');
172 var q_filter = D.get('q_filter');
173 var F = YAHOO.namespace('q_filter');
173 var F = YAHOO.namespace('q_filter');
174
174
175 E.on(q_filter,'click',function(){
175 E.on(q_filter,'click',function(){
176 q_filter.value = '';
176 q_filter.value = '';
177 });
177 });
178
178
179 F.filterTimeout = null;
179 F.filterTimeout = null;
180
180
181 F.updateFilter = function() {
181 F.updateFilter = function() {
182 // Reset timeout
182 // Reset timeout
183 F.filterTimeout = null;
183 F.filterTimeout = null;
184
184
185 var obsolete = [];
185 var obsolete = [];
186 var nodes = S.query('div.table tr td a.repo_name');
186 var nodes = S.query('div.table tr td a.repo_name');
187 var req = D.get('q_filter').value;
187 var req = D.get('q_filter').value;
188 for (n in nodes){
188 for (n in nodes){
189 D.setStyle(nodes[n].parentNode.parentNode,'display','')
189 D.setStyle(nodes[n].parentNode.parentNode,'display','')
190 }
190 }
191 if (req){
191 if (req){
192 for (n in nodes){
192 for (n in nodes){
193 if (nodes[n].innerHTML.toLowerCase().indexOf(req) == -1) {
193 if (nodes[n].innerHTML.toLowerCase().indexOf(req) == -1) {
194 obsolete.push(nodes[n]);
194 obsolete.push(nodes[n]);
195 }
195 }
196 }
196 }
197 if(obsolete){
197 if(obsolete){
198 for (n in obsolete){
198 for (n in obsolete){
199 D.setStyle(obsolete[n].parentNode.parentNode,'display','none');
199 D.setStyle(obsolete[n].parentNode.parentNode,'display','none');
200 }
200 }
201 }
201 }
202 }
202 }
203 }
203 }
204
204
205 E.on(q_filter,'keyup',function(e){
205 E.on(q_filter,'keyup',function(e){
206 clearTimeout(F.filterTimeout);
206 clearTimeout(F.filterTimeout);
207 setTimeout(F.updateFilter,600);
207 setTimeout(F.updateFilter,600);
208 });
208 });
209
209
210 </script>
210 </script>
211 </%def> No newline at end of file
211 </%def>
@@ -1,91 +1,91 b''
1 <%inherit file="/base/base.html"/>
1 <%inherit file="/base/base.html"/>
2
2
3 <%def name="title()">
3 <%def name="title()">
4 ${c.repo_name} ${_('File annotate')} - ${c.rhodecode_name}
4 ${c.repo_name} ${_('File annotate')} - ${c.rhodecode_name}
5 </%def>
5 </%def>
6
6
7 <%def name="breadcrumbs_links()">
7 <%def name="breadcrumbs_links()">
8 ${h.link_to(u'Home',h.url('/'))}
8 ${h.link_to(u'Home',h.url('/'))}
9 &raquo;
9 &raquo;
10 ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
10 ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
11 &raquo;
11 &raquo;
12 ${_('annotate')} @ R${c.cs.revision}:${h.short_id(c.cs.raw_id)}
12 ${_('annotate')} @ R${c.cs.revision}:${h.short_id(c.cs.raw_id)}
13 </%def>
13 </%def>
14
14
15 <%def name="page_nav()">
15 <%def name="page_nav()">
16 ${self.menu('files')}
16 ${self.menu('files')}
17 </%def>
17 </%def>
18 <%def name="main()">
18 <%def name="main()">
19 <div class="box">
19 <div class="box">
20 <!-- box / title -->
20 <!-- box / title -->
21 <div class="title">
21 <div class="title">
22 ${self.breadcrumbs()}
22 ${self.breadcrumbs()}
23 <ul class="links">
23 <ul class="links">
24 <li>
24 <li>
25 <span style="text-transform: uppercase;"><a href="#">${_('branch')}: ${c.cs.branch}</a></span>
25 <span style="text-transform: uppercase;"><a href="#">${_('branch')}: ${c.cs.branch}</a></span>
26 </li>
26 </li>
27 </ul>
27 </ul>
28 </div>
28 </div>
29 <div class="table">
29 <div class="table">
30 <div id="files_data">
30 <div id="files_data">
31 <h3 class="files_location">${_('Location')}: ${h.files_breadcrumbs(c.repo_name,c.cs.revision,c.file.path)}</h3>
31 <h3 class="files_location">${_('Location')}: ${h.files_breadcrumbs(c.repo_name,c.cs.revision,c.file.path)}</h3>
32 <dl class="overview">
32 <dl class="overview">
33 <dt>${_('Revision')}</dt>
33 <dt>${_('Revision')}</dt>
34 <dd>${h.link_to("r%s:%s" % (c.file.last_changeset.revision,h.short_id(c.file.last_changeset.raw_id)),
34 <dd>${h.link_to("r%s:%s" % (c.file.last_changeset.revision,h.short_id(c.file.last_changeset.raw_id)),
35 h.url('changeset_home',repo_name=c.repo_name,revision=c.file.last_changeset.raw_id))} </dd>
35 h.url('changeset_home',repo_name=c.repo_name,revision=c.file.last_changeset.raw_id))} </dd>
36 <dt>${_('Size')}</dt>
36 <dt>${_('Size')}</dt>
37 <dd>${h.format_byte_size(c.file.size,binary=True)}</dd>
37 <dd>${h.format_byte_size(c.file.size,binary=True)}</dd>
38 <dt>${_('Mimetype')}</dt>
38 <dt>${_('Mimetype')}</dt>
39 <dd>${c.file.mimetype}</dd>
39 <dd>${c.file.mimetype}</dd>
40 <dt>${_('Options')}</dt>
40 <dt>${_('Options')}</dt>
41 <dd>${h.link_to(_('show source'),
41 <dd>${h.link_to(_('show source'),
42 h.url('files_home',repo_name=c.repo_name,revision=c.cs.raw_id,f_path=c.f_path))}
42 h.url('files_home',repo_name=c.repo_name,revision=c.cs.raw_id,f_path=c.f_path))}
43 / ${h.link_to(_('show as raw'),
43 / ${h.link_to(_('show as raw'),
44 h.url('files_raw_home',repo_name=c.repo_name,revision=c.cs.raw_id,f_path=c.f_path))}
44 h.url('files_raw_home',repo_name=c.repo_name,revision=c.cs.raw_id,f_path=c.f_path))}
45 / ${h.link_to(_('download as raw'),
45 / ${h.link_to(_('download as raw'),
46 h.url('files_rawfile_home',repo_name=c.repo_name,revision=c.cs.raw_id,f_path=c.f_path))}
46 h.url('files_rawfile_home',repo_name=c.repo_name,revision=c.cs.raw_id,f_path=c.f_path))}
47 </dd>
47 </dd>
48 <dt>${_('History')}</dt>
48 <dt>${_('History')}</dt>
49 <dd>
49 <dd>
50 <div>
50 <div>
51 ${h.form(h.url('files_diff_home',repo_name=c.repo_name,f_path=c.f_path),method='get')}
51 ${h.form(h.url('files_diff_home',repo_name=c.repo_name,f_path=c.f_path),method='get')}
52 ${h.hidden('diff2',c.file.last_changeset.raw_id)}
52 ${h.hidden('diff2',c.file.last_changeset.raw_id)}
53 ${h.select('diff1',c.file.last_changeset.raw_id,c.file_history)}
53 ${h.select('diff1',c.file.last_changeset.raw_id,c.file_history)}
54 ${h.submit('diff','diff to revision',class_="ui-button")}
54 ${h.submit('diff','diff to revision',class_="ui-button")}
55 ${h.submit('show_rev','show at revision',class_="ui-button")}
55 ${h.submit('show_rev','show at revision',class_="ui-button")}
56 ${h.end_form()}
56 ${h.end_form()}
57 </div>
57 </div>
58 </dd>
58 </dd>
59 </dl>
59 </dl>
60 <div id="body" class="codeblock">
60 <div id="body" class="codeblock">
61 <div class="code-header">
61 <div class="code-header">
62 <div class="revision">${c.file.name}@r${c.file.last_changeset.revision}:${h.short_id(c.file.last_changeset.raw_id)}</div>
62 <div class="revision">${c.file.name}@r${c.file.last_changeset.revision}:${h.short_id(c.file.last_changeset.raw_id)}</div>
63 <div class="commit">"${c.file.message}"</div>
63 <div class="commit">"${c.file.message}"</div>
64 </div>
64 </div>
65 <div class="code-body">
65 <div class="code-body">
66 %if c.file.is_binary:
66 %if c.file.is_binary:
67 ${_('Binary file')}
67 ${_('Binary file')}
68 %else:
68 %else:
69 % if c.file.size < c.cut_off_limit:
69 % if c.file.size < c.cut_off_limit:
70 ${h.pygmentize_annotation(c.file,linenos=True,anchorlinenos=True,lineanchors='S',cssclass="code-highlight")}
70 ${h.pygmentize_annotation(c.repo_name,c.file,linenos=True,anchorlinenos=True,lineanchors='S',cssclass="code-highlight")}
71 %else:
71 %else:
72 ${_('File is to big to display')} ${h.link_to(_('show as raw'),
72 ${_('File is to big to display')} ${h.link_to(_('show as raw'),
73 h.url('files_raw_home',repo_name=c.repo_name,revision=c.cs.revision,f_path=c.f_path))}
73 h.url('files_raw_home',repo_name=c.repo_name,revision=c.cs.revision,f_path=c.f_path))}
74 %endif
74 %endif
75 <script type="text/javascript">
75 <script type="text/javascript">
76 YAHOO.util.Event.onDOMReady(function(){
76 YAHOO.util.Event.onDOMReady(function(){
77 YAHOO.util.Event.addListener('show_rev','click',function(e){
77 YAHOO.util.Event.addListener('show_rev','click',function(e){
78 YAHOO.util.Event.preventDefault(e);
78 YAHOO.util.Event.preventDefault(e);
79 var cs = YAHOO.util.Dom.get('diff1').value;
79 var cs = YAHOO.util.Dom.get('diff1').value;
80 var url = "${h.url('files_annotate_home',repo_name=c.repo_name,revision='__CS__',f_path=c.f_path)}".replace('__CS__',cs);
80 var url = "${h.url('files_annotate_home',repo_name=c.repo_name,revision='__CS__',f_path=c.f_path)}".replace('__CS__',cs);
81 window.location = url;
81 window.location = url;
82 });
82 });
83 });
83 });
84 </script>
84 </script>
85 %endif
85 %endif
86 </div>
86 </div>
87 </div>
87 </div>
88 </div>
88 </div>
89 </div>
89 </div>
90 </div>
90 </div>
91 </%def> No newline at end of file
91 </%def>
@@ -1,15 +1,15 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2 %for repo in c.repos_list:
2 %for repo in c.repos_list:
3
3
4 %if repo['dbrepo']['private']:
4 %if repo['dbrepo']['private']:
5 <li>
5 <li>
6 <img src="${h.url("/images/icons/lock.png")}" alt="${_('Private repository')}" class="repo_switcher_type"/>
6 <img src="${h.url("/images/icons/lock.png")}" alt="${_('Private repository')}" class="repo_switcher_type"/>
7 ${h.link_to(repo['name'].name,h.url('summary_home',repo_name=repo['name']),class_="%s" % repo['dbrepo']['repo_type'])}
7 ${h.link_to(repo['name'],h.url('summary_home',repo_name=repo['name']),class_="%s" % repo['dbrepo']['repo_type'])}
8 </li>
8 </li>
9 %else:
9 %else:
10 <li>
10 <li>
11 <img src="${h.url("/images/icons/lock_open.png")}" alt="${_('Public repository')}" class="repo_switcher_type" />
11 <img src="${h.url("/images/icons/lock_open.png")}" alt="${_('Public repository')}" class="repo_switcher_type" />
12 ${h.link_to(repo['name'],h.url('summary_home',repo_name=repo['name']),class_="%s" % repo['dbrepo']['repo_type'])}
12 ${h.link_to(repo['name'],h.url('summary_home',repo_name=repo['name']),class_="%s" % repo['dbrepo']['repo_type'])}
13 </li>
13 </li>
14 %endif
14 %endif
15 %endfor No newline at end of file
15 %endfor
@@ -1,689 +1,692 b''
1 <%inherit file="/base/base.html"/>
1 <%inherit file="/base/base.html"/>
2
2
3 <%def name="title()">
3 <%def name="title()">
4 ${c.repo_name} ${_('Summary')} - ${c.rhodecode_name}
4 ${c.repo_name} ${_('Summary')} - ${c.rhodecode_name}
5 </%def>
5 </%def>
6
6
7 <%def name="breadcrumbs_links()">
7 <%def name="breadcrumbs_links()">
8 ${h.link_to(u'Home',h.url('/'))}
8 ${h.link_to(u'Home',h.url('/'))}
9 &raquo;
9 &raquo;
10 ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
10 ${h.link_to(c.dbrepo.just_name,h.url('summary_home',repo_name=c.repo_name))}
11 &raquo;
11 &raquo;
12 ${_('summary')}
12 ${_('summary')}
13 </%def>
13 </%def>
14
14
15 <%def name="page_nav()">
15 <%def name="page_nav()">
16 ${self.menu('summary')}
16 ${self.menu('summary')}
17 </%def>
17 </%def>
18
18
19 <%def name="main()">
19 <%def name="main()">
20 <div class="box box-left">
20 <div class="box box-left">
21 <!-- box / title -->
21 <!-- box / title -->
22 <div class="title">
22 <div class="title">
23 ${self.breadcrumbs()}
23 ${self.breadcrumbs()}
24 </div>
24 </div>
25 <!-- end box / title -->
25 <!-- end box / title -->
26 <div class="form">
26 <div class="form">
27 <div class="fields">
27 <div class="fields">
28
28
29 <div class="field">
29 <div class="field">
30 <div class="label">
30 <div class="label">
31 <label>${_('Name')}:</label>
31 <label>${_('Name')}:</label>
32 </div>
32 </div>
33 <div class="input-short">
33 <div class="input-short">
34 %if c.rhodecode_user.username != 'default':
35 %if c.following:
36 <span id="follow_toggle" class="following" title="${_('Stop following this repository')}"
37 onclick="javascript:toggleFollowingRepo(this,${c.dbrepo.repo_id},'${str(h.get_token())}')">
38 </span>
39 %else:
40 <span id="follow_toggle" class="follow" title="${_('Start following this repository')}"
41 onclick="javascript:toggleFollowingRepo(this,${c.dbrepo.repo_id},'${str(h.get_token())}')">
42 </span>
43 %endif
44 %endif:
34 %if c.dbrepo.repo_type =='hg':
45 %if c.dbrepo.repo_type =='hg':
35 <img style="margin-bottom:2px" class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="${h.url("/images/icons/hgicon.png")}"/>
46 <img style="margin-bottom:2px" class="icon" title="${_('Mercurial repository')}" alt="${_('Mercurial repository')}" src="${h.url("/images/icons/hgicon.png")}"/>
36 %endif
47 %endif
37 %if c.dbrepo.repo_type =='git':
48 %if c.dbrepo.repo_type =='git':
38 <img style="margin-bottom:2px" class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="${h.url("/images/icons/giticon.png")}"/>
49 <img style="margin-bottom:2px" class="icon" title="${_('Git repository')}" alt="${_('Git repository')}" src="${h.url("/images/icons/giticon.png")}"/>
39 %endif
50 %endif
40
51
41 %if c.dbrepo.private:
52 %if c.dbrepo.private:
42 <img style="margin-bottom:2px" class="icon" title="${_('private repository')}" alt="${_('private repository')}" src="${h.url("/images/icons/lock.png")}"/>
53 <img style="margin-bottom:2px" class="icon" title="${_('private repository')}" alt="${_('private repository')}" src="${h.url("/images/icons/lock.png")}"/>
43 %else:
54 %else:
44 <img style="margin-bottom:2px" class="icon" title="${_('public repository')}" alt="${_('public repository')}" src="${h.url("/images/icons/lock_open.png")}"/>
55 <img style="margin-bottom:2px" class="icon" title="${_('public repository')}" alt="${_('public repository')}" src="${h.url("/images/icons/lock_open.png")}"/>
45 %endif
56 %endif
46 <span style="font-size: 1.6em;font-weight: bold;vertical-align: baseline;">${c.repo.name}</span>
57 <span style="font-size: 1.6em;font-weight: bold;vertical-align: baseline;">${h.repo_link(c.dbrepo.groups_and_repo)}</span>
47 %if c.rhodecode_user.username != 'default':
58
48 %if c.following:
49 <span id="follow_toggle" class="following" title="${_('Stop following this repository')}"
50 onclick="javascript:toggleFollowingRepo(this,${c.dbrepo.repo_id},'${str(h.get_token())}')">
51 </span>
52 %else:
53 <span id="follow_toggle" class="follow" title="${_('Start following this repository')}"
54 onclick="javascript:toggleFollowingRepo(this,${c.dbrepo.repo_id},'${str(h.get_token())}')">
55 </span>
56 %endif
57 %endif:
58 <br/>
59 %if c.dbrepo.fork:
59 %if c.dbrepo.fork:
60 <span style="margin-top:5px">
60 <span style="margin-top:5px">
61 <a href="${h.url('summary_home',repo_name=c.dbrepo.fork.repo_name)}">
61 <a href="${h.url('summary_home',repo_name=c.dbrepo.fork.repo_name)}">
62 <img class="icon" alt="${_('public')}"
62 <img class="icon" alt="${_('public')}"
63 title="${_('Fork of')} ${c.dbrepo.fork.repo_name}"
63 title="${_('Fork of')} ${c.dbrepo.fork.repo_name}"
64 src="${h.url("/images/icons/arrow_divide.png")}"/>
64 src="${h.url("/images/icons/arrow_divide.png")}"/>
65 ${_('Fork of')} ${c.dbrepo.fork.repo_name}
65 ${_('Fork of')} ${c.dbrepo.fork.repo_name}
66 </a>
66 </a>
67 </span>
67 </span>
68 %endif
68 %endif
69 %if c.dbrepo.clone_uri:
69 %if c.dbrepo.clone_uri:
70 <span style="margin-top:5px">
70 <span style="margin-top:5px">
71 <a href="${h.url(str(c.dbrepo.clone_uri))}">
71 <a href="${h.url(str(c.dbrepo.clone_uri))}">
72 <img class="icon" alt="${_('remote clone')}"
72 <img class="icon" alt="${_('remote clone')}"
73 title="${_('Clone from')} ${c.dbrepo.clone_uri}"
73 title="${_('Clone from')} ${c.dbrepo.clone_uri}"
74 src="${h.url("/images/icons/connect.png")}"/>
74 src="${h.url("/images/icons/connect.png")}"/>
75 ${_('Clone from')} ${c.dbrepo.clone_uri}
75 ${_('Clone from')} ${c.dbrepo.clone_uri}
76 </a>
76 </a>
77 </span>
77 </span>
78 %endif
78 %endif
79 </div>
79 </div>
80 </div>
80 </div>
81
81
82
82
83 <div class="field">
83 <div class="field">
84 <div class="label">
84 <div class="label">
85 <label>${_('Description')}:</label>
85 <label>${_('Description')}:</label>
86 </div>
86 </div>
87 <div class="input-short">
87 <div class="input-short">
88 ${c.dbrepo.description}
88 ${c.dbrepo.description}
89 </div>
89 </div>
90 </div>
90 </div>
91
91
92
92
93 <div class="field">
93 <div class="field">
94 <div class="label">
94 <div class="label">
95 <label>${_('Contact')}:</label>
95 <label>${_('Contact')}:</label>
96 </div>
96 </div>
97 <div class="input-short">
97 <div class="input-short">
98 <div class="gravatar">
98 <div class="gravatar">
99 <img alt="gravatar" src="${h.gravatar_url(c.dbrepo.user.email)}"/>
99 <img alt="gravatar" src="${h.gravatar_url(c.dbrepo.user.email)}"/>
100 </div>
100 </div>
101 ${_('Username')}: ${c.dbrepo.user.username}<br/>
101 ${_('Username')}: ${c.dbrepo.user.username}<br/>
102 ${_('Name')}: ${c.dbrepo.user.name} ${c.dbrepo.user.lastname}<br/>
102 ${_('Name')}: ${c.dbrepo.user.name} ${c.dbrepo.user.lastname}<br/>
103 ${_('Email')}: <a href="mailto:${c.dbrepo.user.email}">${c.dbrepo.user.email}</a>
103 ${_('Email')}: <a href="mailto:${c.dbrepo.user.email}">${c.dbrepo.user.email}</a>
104 </div>
104 </div>
105 </div>
105 </div>
106
106
107 <div class="field">
107 <div class="field">
108 <div class="label">
108 <div class="label">
109 <label>${_('Last change')}:</label>
109 <label>${_('Last change')}:</label>
110 </div>
110 </div>
111 <div class="input-short">
111 <div class="input-short">
112 ${h.age(c.repo.last_change)} - ${c.repo.last_change}
112 <b>${'r%s:%s' % (h.get_changeset_safe(c.rhodecode_repo,'tip').revision,
113 ${_('by')} ${h.get_changeset_safe(c.repo,'tip').author}
113 h.get_changeset_safe(c.rhodecode_repo,'tip').short_id)}</b> -
114 <span class="tooltip" title="${c.rhodecode_repo.last_change}">
115 ${h.age(c.rhodecode_repo.last_change)}</span><br/>
116 ${_('by')} ${h.get_changeset_safe(c.rhodecode_repo,'tip').author}
114
117
115 </div>
118 </div>
116 </div>
119 </div>
117
120
118 <div class="field">
121 <div class="field">
119 <div class="label">
122 <div class="label">
120 <label>${_('Clone url')}:</label>
123 <label>${_('Clone url')}:</label>
121 </div>
124 </div>
122 <div class="input-short">
125 <div class="input-short">
123 <input type="text" id="clone_url" readonly="readonly" value="hg clone ${c.clone_repo_url}" size="70"/>
126 <input type="text" id="clone_url" readonly="readonly" value="hg clone ${c.clone_repo_url}" size="70"/>
124 </div>
127 </div>
125 </div>
128 </div>
126
129
127 <div class="field">
130 <div class="field">
128 <div class="label">
131 <div class="label">
129 <label>${_('Trending source files')}:</label>
132 <label>${_('Trending source files')}:</label>
130 </div>
133 </div>
131 <div class="input-short">
134 <div class="input-short">
132 <div id="lang_stats"></div>
135 <div id="lang_stats"></div>
133 </div>
136 </div>
134 </div>
137 </div>
135
138
136 <div class="field">
139 <div class="field">
137 <div class="label">
140 <div class="label">
138 <label>${_('Download')}:</label>
141 <label>${_('Download')}:</label>
139 </div>
142 </div>
140 <div class="input-short">
143 <div class="input-short">
141 %if len(c.repo.revisions) == 0:
144 %if len(c.rhodecode_repo.revisions) == 0:
142 ${_('There are no downloads yet')}
145 ${_('There are no downloads yet')}
143 %elif c.enable_downloads is False:
146 %elif c.enable_downloads is False:
144 ${_('Downloads are disabled for this repository')}
147 ${_('Downloads are disabled for this repository')}
145 %if h.HasPermissionAll('hg.admin')('enable stats on from summary'):
148 %if h.HasPermissionAll('hg.admin')('enable stats on from summary'):
146 [${h.link_to(_('enable'),h.url('edit_repo',repo_name=c.repo_name))}]
149 [${h.link_to(_('enable'),h.url('edit_repo',repo_name=c.repo_name))}]
147 %endif
150 %endif
148 %else:
151 %else:
149 ${h.select('download_options',c.repo.get_changeset().raw_id,c.download_options)}
152 ${h.select('download_options',c.rhodecode_repo.get_changeset().raw_id,c.download_options)}
150 %for cnt,archive in enumerate(c.repo._get_archives()):
153 %for cnt,archive in enumerate(c.rhodecode_repo._get_archives()):
151 %if cnt >=1:
154 %if cnt >=1:
152 |
155 |
153 %endif
156 %endif
154 <span class="tooltip" title="${_('Download %s as %s') %('tip',archive['type'])}"
157 <span class="tooltip" title="${_('Download %s as %s') %('tip',archive['type'])}"
155 id="${archive['type']+'_link'}">${h.link_to(archive['type'],
158 id="${archive['type']+'_link'}">${h.link_to(archive['type'],
156 h.url('files_archive_home',repo_name=c.repo.name,
159 h.url('files_archive_home',repo_name=c.dbrepo.repo_name,
157 fname='tip'+archive['extension']),class_="archive_icon")}</span>
160 fname='tip'+archive['extension']),class_="archive_icon")}</span>
158 %endfor
161 %endfor
159 %endif
162 %endif
160 </div>
163 </div>
161 </div>
164 </div>
162
165
163 <div class="field">
166 <div class="field">
164 <div class="label">
167 <div class="label">
165 <label>${_('Feeds')}:</label>
168 <label>${_('Feeds')}:</label>
166 </div>
169 </div>
167 <div class="input-short">
170 <div class="input-short">
168 %if c.rhodecode_user.username != 'default':
171 %if c.rhodecode_user.username != 'default':
169 ${h.link_to(_('RSS'),h.url('rss_feed_home',repo_name=c.repo.name,api_key=c.rhodecode_user.api_key),class_='rss_icon')}
172 ${h.link_to(_('RSS'),h.url('rss_feed_home',repo_name=c.dbrepo.repo_name,api_key=c.rhodecode_user.api_key),class_='rss_icon')}
170 ${h.link_to(_('Atom'),h.url('atom_feed_home',repo_name=c.repo.name,api_key=c.rhodecode_user.api_key),class_='atom_icon')}
173 ${h.link_to(_('Atom'),h.url('atom_feed_home',repo_name=c.dbrepo.repo_name,api_key=c.rhodecode_user.api_key),class_='atom_icon')}
171 %else:
174 %else:
172 ${h.link_to(_('RSS'),h.url('rss_feed_home',repo_name=c.repo.name),class_='rss_icon')}
175 ${h.link_to(_('RSS'),h.url('rss_feed_home',repo_name=c.dbrepo.repo_name),class_='rss_icon')}
173 ${h.link_to(_('Atom'),h.url('atom_feed_home',repo_name=c.repo.name),class_='atom_icon')}
176 ${h.link_to(_('Atom'),h.url('atom_feed_home',repo_name=c.dbrepo.repo_name),class_='atom_icon')}
174 %endif
177 %endif
175 </div>
178 </div>
176 </div>
179 </div>
177 </div>
180 </div>
178 </div>
181 </div>
179 <script type="text/javascript">
182 <script type="text/javascript">
180 YUE.onDOMReady(function(e){
183 YUE.onDOMReady(function(e){
181 id = 'clone_url';
184 id = 'clone_url';
182 YUE.on(id,'click',function(e){
185 YUE.on(id,'click',function(e){
183 YUD.get('clone_url').select();
186 YUD.get('clone_url').select();
184 })
187 })
185 })
188 })
186 var data = ${c.trending_languages|n};
189 var data = ${c.trending_languages|n};
187 var total = 0;
190 var total = 0;
188 var no_data = true;
191 var no_data = true;
189 for (k in data){
192 for (k in data){
190 total += data[k];
193 total += data[k];
191 no_data = false;
194 no_data = false;
192 }
195 }
193 var tbl = document.createElement('table');
196 var tbl = document.createElement('table');
194 tbl.setAttribute('class','trending_language_tbl');
197 tbl.setAttribute('class','trending_language_tbl');
195 var cnt =0;
198 var cnt =0;
196 for (k in data){
199 for (k in data){
197 cnt+=1;
200 cnt+=1;
198 var hide = cnt>2;
201 var hide = cnt>2;
199 var tr = document.createElement('tr');
202 var tr = document.createElement('tr');
200 if (hide){
203 if (hide){
201 tr.setAttribute('style','display:none');
204 tr.setAttribute('style','display:none');
202 tr.setAttribute('class','stats_hidden');
205 tr.setAttribute('class','stats_hidden');
203 }
206 }
204 var percentage = Math.round((data[k]/total*100),2);
207 var percentage = Math.round((data[k]/total*100),2);
205 var value = data[k];
208 var value = data[k];
206 var td1 = document.createElement('td');
209 var td1 = document.createElement('td');
207 td1.width=150;
210 td1.width=150;
208 var trending_language_label = document.createElement('div');
211 var trending_language_label = document.createElement('div');
209 trending_language_label.innerHTML = k;
212 trending_language_label.innerHTML = k;
210 td1.appendChild(trending_language_label);
213 td1.appendChild(trending_language_label);
211
214
212 var td2 = document.createElement('td');
215 var td2 = document.createElement('td');
213 td2.setAttribute('style','padding-right:14px !important');
216 td2.setAttribute('style','padding-right:14px !important');
214 var trending_language = document.createElement('div');
217 var trending_language = document.createElement('div');
215 var nr_files = value+" ${_('files')}";
218 var nr_files = value+" ${_('files')}";
216
219
217 trending_language.title = k+" "+nr_files;
220 trending_language.title = k+" "+nr_files;
218
221
219 if (percentage>20){
222 if (percentage>20){
220 trending_language.innerHTML = "<b style='font-size:0.8em'>"+percentage+"% "+nr_files+ "</b>";
223 trending_language.innerHTML = "<b style='font-size:0.8em'>"+percentage+"% "+nr_files+ "</b>";
221 }
224 }
222 else{
225 else{
223 trending_language.innerHTML = "<b style='font-size:0.8em'>"+percentage+"%</b>";
226 trending_language.innerHTML = "<b style='font-size:0.8em'>"+percentage+"%</b>";
224 }
227 }
225
228
226 trending_language.setAttribute("class", 'trending_language top-right-rounded-corner bottom-right-rounded-corner');
229 trending_language.setAttribute("class", 'trending_language top-right-rounded-corner bottom-right-rounded-corner');
227 trending_language.style.width=percentage+"%";
230 trending_language.style.width=percentage+"%";
228 td2.appendChild(trending_language);
231 td2.appendChild(trending_language);
229
232
230 tr.appendChild(td1);
233 tr.appendChild(td1);
231 tr.appendChild(td2);
234 tr.appendChild(td2);
232 tbl.appendChild(tr);
235 tbl.appendChild(tr);
233 if(cnt == 2){
236 if(cnt == 2){
234 var show_more = document.createElement('tr');
237 var show_more = document.createElement('tr');
235 var td=document.createElement('td');
238 var td=document.createElement('td');
236 lnk = document.createElement('a');
239 lnk = document.createElement('a');
237 lnk.href='#';
240 lnk.href='#';
238 lnk.innerHTML = "${_("show more")}";
241 lnk.innerHTML = "${_("show more")}";
239 lnk.id='code_stats_show_more';
242 lnk.id='code_stats_show_more';
240 td.appendChild(lnk);
243 td.appendChild(lnk);
241 show_more.appendChild(td);
244 show_more.appendChild(td);
242 show_more.appendChild(document.createElement('td'));
245 show_more.appendChild(document.createElement('td'));
243 tbl.appendChild(show_more);
246 tbl.appendChild(show_more);
244 }
247 }
245
248
246 }
249 }
247 if(no_data){
250 if(no_data){
248 var tr = document.createElement('tr');
251 var tr = document.createElement('tr');
249 var td1 = document.createElement('td');
252 var td1 = document.createElement('td');
250 td1.innerHTML = "${c.no_data_msg}";
253 td1.innerHTML = "${c.no_data_msg}";
251 tr.appendChild(td1);
254 tr.appendChild(td1);
252 tbl.appendChild(tr);
255 tbl.appendChild(tr);
253 }
256 }
254 YUD.get('lang_stats').appendChild(tbl);
257 YUD.get('lang_stats').appendChild(tbl);
255 YUE.on('code_stats_show_more','click',function(){
258 YUE.on('code_stats_show_more','click',function(){
256 l = YUD.getElementsByClassName('stats_hidden')
259 l = YUD.getElementsByClassName('stats_hidden')
257 for (e in l){
260 for (e in l){
258 YUD.setStyle(l[e],'display','');
261 YUD.setStyle(l[e],'display','');
259 };
262 };
260 YUD.setStyle(YUD.get('code_stats_show_more'),
263 YUD.setStyle(YUD.get('code_stats_show_more'),
261 'display','none');
264 'display','none');
262 })
265 })
263
266
264
267
265 YUE.on('download_options','change',function(e){
268 YUE.on('download_options','change',function(e){
266 var new_cs = e.target.options[e.target.selectedIndex];
269 var new_cs = e.target.options[e.target.selectedIndex];
267 var tmpl_links = {}
270 var tmpl_links = {}
268 %for cnt,archive in enumerate(c.repo._get_archives()):
271 %for cnt,archive in enumerate(c.rhodecode_repo._get_archives()):
269 tmpl_links['${archive['type']}'] = '${h.link_to(archive['type'],
272 tmpl_links['${archive['type']}'] = '${h.link_to(archive['type'],
270 h.url('files_archive_home',repo_name=c.repo.name,
273 h.url('files_archive_home',repo_name=c.dbrepo.repo_name,
271 fname='__CS__'+archive['extension']),class_="archive_icon")}';
274 fname='__CS__'+archive['extension']),class_="archive_icon")}';
272 %endfor
275 %endfor
273
276
274
277
275 for(k in tmpl_links){
278 for(k in tmpl_links){
276 var s = YUD.get(k+'_link')
279 var s = YUD.get(k+'_link')
277 title_tmpl = "${_('Download %s as %s') % ('__CS_NAME__',archive['type'])}";
280 title_tmpl = "${_('Download %s as %s') % ('__CS_NAME__',archive['type'])}";
278 s.title = title_tmpl.replace('__CS_NAME__',new_cs.text)
281 s.title = title_tmpl.replace('__CS_NAME__',new_cs.text)
279 s.innerHTML = tmpl_links[k].replace('__CS__',new_cs.value);
282 s.innerHTML = tmpl_links[k].replace('__CS__',new_cs.value);
280 }
283 }
281
284
282 })
285 })
283
286
284 </script>
287 </script>
285 </div>
288 </div>
286
289
287 <div class="box box-right" style="min-height:455px">
290 <div class="box box-right" style="min-height:455px">
288 <!-- box / title -->
291 <!-- box / title -->
289 <div class="title">
292 <div class="title">
290 <h5>${_('Commit activity by day / author')}</h5>
293 <h5>${_('Commit activity by day / author')}</h5>
291 </div>
294 </div>
292
295
293 <div class="table">
296 <div class="table">
294 %if c.no_data:
297 %if c.no_data:
295 <div style="padding:0 10px 10px 15px;font-size: 1.2em;">${c.no_data_msg}
298 <div style="padding:0 10px 10px 15px;font-size: 1.2em;">${c.no_data_msg}
296 %if h.HasPermissionAll('hg.admin')('enable stats on from summary'):
299 %if h.HasPermissionAll('hg.admin')('enable stats on from summary'):
297 [${h.link_to(_('enable'),h.url('edit_repo',repo_name=c.repo_name))}]
300 [${h.link_to(_('enable'),h.url('edit_repo',repo_name=c.repo_name))}]
298 %endif
301 %endif
299 </div>
302 </div>
300 %endif:
303 %endif:
301 <div id="commit_history" style="width:460px;height:300px;float:left"></div>
304 <div id="commit_history" style="width:460px;height:300px;float:left"></div>
302 <div style="clear: both;height: 10px"></div>
305 <div style="clear: both;height: 10px"></div>
303 <div id="overview" style="width:460px;height:100px;float:left"></div>
306 <div id="overview" style="width:460px;height:100px;float:left"></div>
304
307
305 <div id="legend_data" style="clear:both;margin-top:10px;">
308 <div id="legend_data" style="clear:both;margin-top:10px;">
306 <div id="legend_container"></div>
309 <div id="legend_container"></div>
307 <div id="legend_choices">
310 <div id="legend_choices">
308 <table id="legend_choices_tables" style="font-size:smaller;color:#545454"></table>
311 <table id="legend_choices_tables" style="font-size:smaller;color:#545454"></table>
309 </div>
312 </div>
310 </div>
313 </div>
311 <script type="text/javascript">
314 <script type="text/javascript">
312 /**
315 /**
313 * Plots summary graph
316 * Plots summary graph
314 *
317 *
315 * @class SummaryPlot
318 * @class SummaryPlot
316 * @param {from} initial from for detailed graph
319 * @param {from} initial from for detailed graph
317 * @param {to} initial to for detailed graph
320 * @param {to} initial to for detailed graph
318 * @param {dataset}
321 * @param {dataset}
319 * @param {overview_dataset}
322 * @param {overview_dataset}
320 */
323 */
321 function SummaryPlot(from,to,dataset,overview_dataset) {
324 function SummaryPlot(from,to,dataset,overview_dataset) {
322 var initial_ranges = {
325 var initial_ranges = {
323 "xaxis":{
326 "xaxis":{
324 "from":from,
327 "from":from,
325 "to":to,
328 "to":to,
326 },
329 },
327 };
330 };
328 var dataset = dataset;
331 var dataset = dataset;
329 var overview_dataset = [overview_dataset];
332 var overview_dataset = [overview_dataset];
330 var choiceContainer = YUD.get("legend_choices");
333 var choiceContainer = YUD.get("legend_choices");
331 var choiceContainerTable = YUD.get("legend_choices_tables");
334 var choiceContainerTable = YUD.get("legend_choices_tables");
332 var plotContainer = YUD.get('commit_history');
335 var plotContainer = YUD.get('commit_history');
333 var overviewContainer = YUD.get('overview');
336 var overviewContainer = YUD.get('overview');
334
337
335 var plot_options = {
338 var plot_options = {
336 bars: {show:true,align:'center',lineWidth:4},
339 bars: {show:true,align:'center',lineWidth:4},
337 legend: {show:true, container:"legend_container"},
340 legend: {show:true, container:"legend_container"},
338 points: {show:true,radius:0,fill:false},
341 points: {show:true,radius:0,fill:false},
339 yaxis: {tickDecimals:0,},
342 yaxis: {tickDecimals:0,},
340 xaxis: {
343 xaxis: {
341 mode: "time",
344 mode: "time",
342 timeformat: "%d/%m",
345 timeformat: "%d/%m",
343 min:from,
346 min:from,
344 max:to,
347 max:to,
345 },
348 },
346 grid: {
349 grid: {
347 hoverable: true,
350 hoverable: true,
348 clickable: true,
351 clickable: true,
349 autoHighlight:true,
352 autoHighlight:true,
350 color: "#999"
353 color: "#999"
351 },
354 },
352 //selection: {mode: "x"}
355 //selection: {mode: "x"}
353 };
356 };
354 var overview_options = {
357 var overview_options = {
355 legend:{show:false},
358 legend:{show:false},
356 bars: {show:true,barWidth: 2,},
359 bars: {show:true,barWidth: 2,},
357 shadowSize: 0,
360 shadowSize: 0,
358 xaxis: {mode: "time", timeformat: "%d/%m/%y",},
361 xaxis: {mode: "time", timeformat: "%d/%m/%y",},
359 yaxis: {ticks: 3, min: 0,tickDecimals:0,},
362 yaxis: {ticks: 3, min: 0,tickDecimals:0,},
360 grid: {color: "#999",},
363 grid: {color: "#999",},
361 selection: {mode: "x"}
364 selection: {mode: "x"}
362 };
365 };
363
366
364 /**
367 /**
365 *get dummy data needed in few places
368 *get dummy data needed in few places
366 */
369 */
367 function getDummyData(label){
370 function getDummyData(label){
368 return {"label":label,
371 return {"label":label,
369 "data":[{"time":0,
372 "data":[{"time":0,
370 "commits":0,
373 "commits":0,
371 "added":0,
374 "added":0,
372 "changed":0,
375 "changed":0,
373 "removed":0,
376 "removed":0,
374 }],
377 }],
375 "schema":["commits"],
378 "schema":["commits"],
376 "color":'#ffffff',
379 "color":'#ffffff',
377 }
380 }
378 }
381 }
379
382
380 /**
383 /**
381 * generate checkboxes accordindly to data
384 * generate checkboxes accordindly to data
382 * @param keys
385 * @param keys
383 * @returns
386 * @returns
384 */
387 */
385 function generateCheckboxes(data) {
388 function generateCheckboxes(data) {
386 //append checkboxes
389 //append checkboxes
387 var i = 0;
390 var i = 0;
388 choiceContainerTable.innerHTML = '';
391 choiceContainerTable.innerHTML = '';
389 for(var pos in data) {
392 for(var pos in data) {
390
393
391 data[pos].color = i;
394 data[pos].color = i;
392 i++;
395 i++;
393 if(data[pos].label != ''){
396 if(data[pos].label != ''){
394 choiceContainerTable.innerHTML += '<tr><td>'+
397 choiceContainerTable.innerHTML += '<tr><td>'+
395 '<input type="checkbox" name="' + data[pos].label +'" checked="checked" />'
398 '<input type="checkbox" name="' + data[pos].label +'" checked="checked" />'
396 +data[pos].label+
399 +data[pos].label+
397 '</td></tr>';
400 '</td></tr>';
398 }
401 }
399 }
402 }
400 }
403 }
401
404
402 /**
405 /**
403 * ToolTip show
406 * ToolTip show
404 */
407 */
405 function showTooltip(x, y, contents) {
408 function showTooltip(x, y, contents) {
406 var div=document.getElementById('tooltip');
409 var div=document.getElementById('tooltip');
407 if(!div) {
410 if(!div) {
408 div = document.createElement('div');
411 div = document.createElement('div');
409 div.id="tooltip";
412 div.id="tooltip";
410 div.style.position="absolute";
413 div.style.position="absolute";
411 div.style.border='1px solid #fdd';
414 div.style.border='1px solid #fdd';
412 div.style.padding='2px';
415 div.style.padding='2px';
413 div.style.backgroundColor='#fee';
416 div.style.backgroundColor='#fee';
414 document.body.appendChild(div);
417 document.body.appendChild(div);
415 }
418 }
416 YUD.setStyle(div, 'opacity', 0);
419 YUD.setStyle(div, 'opacity', 0);
417 div.innerHTML = contents;
420 div.innerHTML = contents;
418 div.style.top=(y + 5) + "px";
421 div.style.top=(y + 5) + "px";
419 div.style.left=(x + 5) + "px";
422 div.style.left=(x + 5) + "px";
420
423
421 var anim = new YAHOO.util.Anim(div, {opacity: {to: 0.8}}, 0.2);
424 var anim = new YAHOO.util.Anim(div, {opacity: {to: 0.8}}, 0.2);
422 anim.animate();
425 anim.animate();
423 }
426 }
424
427
425 /**
428 /**
426 * This function will detect if selected period has some changesets
429 * This function will detect if selected period has some changesets
427 for this user if it does this data is then pushed for displaying
430 for this user if it does this data is then pushed for displaying
428 Additionally it will only display users that are selected by the checkbox
431 Additionally it will only display users that are selected by the checkbox
429 */
432 */
430 function getDataAccordingToRanges(ranges) {
433 function getDataAccordingToRanges(ranges) {
431
434
432 var data = [];
435 var data = [];
433 var keys = [];
436 var keys = [];
434 for(var key in dataset){
437 for(var key in dataset){
435 var push = false;
438 var push = false;
436
439
437 //method1 slow !!
440 //method1 slow !!
438 //*
441 //*
439 for(var ds in dataset[key].data){
442 for(var ds in dataset[key].data){
440 commit_data = dataset[key].data[ds];
443 commit_data = dataset[key].data[ds];
441 if (commit_data.time >= ranges.xaxis.from && commit_data.time <= ranges.xaxis.to){
444 if (commit_data.time >= ranges.xaxis.from && commit_data.time <= ranges.xaxis.to){
442 push = true;
445 push = true;
443 break;
446 break;
444 }
447 }
445 }
448 }
446 //*/
449 //*/
447
450
448 /*//method2 sorted commit data !!!
451 /*//method2 sorted commit data !!!
449
452
450 var first_commit = dataset[key].data[0].time;
453 var first_commit = dataset[key].data[0].time;
451 var last_commit = dataset[key].data[dataset[key].data.length-1].time;
454 var last_commit = dataset[key].data[dataset[key].data.length-1].time;
452
455
453 if (first_commit >= ranges.xaxis.from && last_commit <= ranges.xaxis.to){
456 if (first_commit >= ranges.xaxis.from && last_commit <= ranges.xaxis.to){
454 push = true;
457 push = true;
455 }
458 }
456 //*/
459 //*/
457
460
458 if(push){
461 if(push){
459 data.push(dataset[key]);
462 data.push(dataset[key]);
460 }
463 }
461 }
464 }
462 if(data.length >= 1){
465 if(data.length >= 1){
463 return data;
466 return data;
464 }
467 }
465 else{
468 else{
466 //just return dummy data for graph to plot itself
469 //just return dummy data for graph to plot itself
467 return [getDummyData('')];
470 return [getDummyData('')];
468 }
471 }
469
472
470 }
473 }
471
474
472 /**
475 /**
473 * redraw using new checkbox data
476 * redraw using new checkbox data
474 */
477 */
475 function plotchoiced(e,args){
478 function plotchoiced(e,args){
476 var cur_data = args[0];
479 var cur_data = args[0];
477 var cur_ranges = args[1];
480 var cur_ranges = args[1];
478
481
479 var new_data = [];
482 var new_data = [];
480 var inputs = choiceContainer.getElementsByTagName("input");
483 var inputs = choiceContainer.getElementsByTagName("input");
481
484
482 //show only checked labels
485 //show only checked labels
483 for(var i=0; i<inputs.length; i++) {
486 for(var i=0; i<inputs.length; i++) {
484 var checkbox_key = inputs[i].name;
487 var checkbox_key = inputs[i].name;
485
488
486 if(inputs[i].checked){
489 if(inputs[i].checked){
487 for(var d in cur_data){
490 for(var d in cur_data){
488 if(cur_data[d].label == checkbox_key){
491 if(cur_data[d].label == checkbox_key){
489 new_data.push(cur_data[d]);
492 new_data.push(cur_data[d]);
490 }
493 }
491 }
494 }
492 }
495 }
493 else{
496 else{
494 //push dummy data to not hide the label
497 //push dummy data to not hide the label
495 new_data.push(getDummyData(checkbox_key));
498 new_data.push(getDummyData(checkbox_key));
496 }
499 }
497 }
500 }
498
501
499 var new_options = YAHOO.lang.merge(plot_options, {
502 var new_options = YAHOO.lang.merge(plot_options, {
500 xaxis: {
503 xaxis: {
501 min: cur_ranges.xaxis.from,
504 min: cur_ranges.xaxis.from,
502 max: cur_ranges.xaxis.to,
505 max: cur_ranges.xaxis.to,
503 mode:"time",
506 mode:"time",
504 timeformat: "%d/%m",
507 timeformat: "%d/%m",
505 },
508 },
506 });
509 });
507 if (!new_data){
510 if (!new_data){
508 new_data = [[0,1]];
511 new_data = [[0,1]];
509 }
512 }
510 // do the zooming
513 // do the zooming
511 plot = YAHOO.widget.Flot(plotContainer, new_data, new_options);
514 plot = YAHOO.widget.Flot(plotContainer, new_data, new_options);
512
515
513 plot.subscribe("plotselected", plotselected);
516 plot.subscribe("plotselected", plotselected);
514
517
515 //resubscribe plothover
518 //resubscribe plothover
516 plot.subscribe("plothover", plothover);
519 plot.subscribe("plothover", plothover);
517
520
518 // don't fire event on the overview to prevent eternal loop
521 // don't fire event on the overview to prevent eternal loop
519 overview.setSelection(cur_ranges, true);
522 overview.setSelection(cur_ranges, true);
520
523
521 }
524 }
522
525
523 /**
526 /**
524 * plot only selected items from overview
527 * plot only selected items from overview
525 * @param ranges
528 * @param ranges
526 * @returns
529 * @returns
527 */
530 */
528 function plotselected(ranges,cur_data) {
531 function plotselected(ranges,cur_data) {
529 //updates the data for new plot
532 //updates the data for new plot
530 data = getDataAccordingToRanges(ranges);
533 data = getDataAccordingToRanges(ranges);
531 generateCheckboxes(data);
534 generateCheckboxes(data);
532
535
533 var new_options = YAHOO.lang.merge(plot_options, {
536 var new_options = YAHOO.lang.merge(plot_options, {
534 xaxis: {
537 xaxis: {
535 min: ranges.xaxis.from,
538 min: ranges.xaxis.from,
536 max: ranges.xaxis.to,
539 max: ranges.xaxis.to,
537 mode:"time",
540 mode:"time",
538 timeformat: "%d/%m",
541 timeformat: "%d/%m",
539 },
542 },
540 yaxis: {
543 yaxis: {
541 min: ranges.yaxis.from,
544 min: ranges.yaxis.from,
542 max: ranges.yaxis.to,
545 max: ranges.yaxis.to,
543 },
546 },
544
547
545 });
548 });
546 // do the zooming
549 // do the zooming
547 plot = YAHOO.widget.Flot(plotContainer, data, new_options);
550 plot = YAHOO.widget.Flot(plotContainer, data, new_options);
548
551
549 plot.subscribe("plotselected", plotselected);
552 plot.subscribe("plotselected", plotselected);
550
553
551 //resubscribe plothover
554 //resubscribe plothover
552 plot.subscribe("plothover", plothover);
555 plot.subscribe("plothover", plothover);
553
556
554 // don't fire event on the overview to prevent eternal loop
557 // don't fire event on the overview to prevent eternal loop
555 overview.setSelection(ranges, true);
558 overview.setSelection(ranges, true);
556
559
557 //resubscribe choiced
560 //resubscribe choiced
558 YUE.on(choiceContainer.getElementsByTagName("input"), "click", plotchoiced, [data, ranges]);
561 YUE.on(choiceContainer.getElementsByTagName("input"), "click", plotchoiced, [data, ranges]);
559 }
562 }
560
563
561 var previousPoint = null;
564 var previousPoint = null;
562
565
563 function plothover(o) {
566 function plothover(o) {
564 var pos = o.pos;
567 var pos = o.pos;
565 var item = o.item;
568 var item = o.item;
566
569
567 //YUD.get("x").innerHTML = pos.x.toFixed(2);
570 //YUD.get("x").innerHTML = pos.x.toFixed(2);
568 //YUD.get("y").innerHTML = pos.y.toFixed(2);
571 //YUD.get("y").innerHTML = pos.y.toFixed(2);
569 if (item) {
572 if (item) {
570 if (previousPoint != item.datapoint) {
573 if (previousPoint != item.datapoint) {
571 previousPoint = item.datapoint;
574 previousPoint = item.datapoint;
572
575
573 var tooltip = YUD.get("tooltip");
576 var tooltip = YUD.get("tooltip");
574 if(tooltip) {
577 if(tooltip) {
575 tooltip.parentNode.removeChild(tooltip);
578 tooltip.parentNode.removeChild(tooltip);
576 }
579 }
577 var x = item.datapoint.x.toFixed(2);
580 var x = item.datapoint.x.toFixed(2);
578 var y = item.datapoint.y.toFixed(2);
581 var y = item.datapoint.y.toFixed(2);
579
582
580 if (!item.series.label){
583 if (!item.series.label){
581 item.series.label = 'commits';
584 item.series.label = 'commits';
582 }
585 }
583 var d = new Date(x*1000);
586 var d = new Date(x*1000);
584 var fd = d.toDateString()
587 var fd = d.toDateString()
585 var nr_commits = parseInt(y);
588 var nr_commits = parseInt(y);
586
589
587 var cur_data = dataset[item.series.label].data[item.dataIndex];
590 var cur_data = dataset[item.series.label].data[item.dataIndex];
588 var added = cur_data.added;
591 var added = cur_data.added;
589 var changed = cur_data.changed;
592 var changed = cur_data.changed;
590 var removed = cur_data.removed;
593 var removed = cur_data.removed;
591
594
592 var nr_commits_suffix = " ${_('commits')} ";
595 var nr_commits_suffix = " ${_('commits')} ";
593 var added_suffix = " ${_('files added')} ";
596 var added_suffix = " ${_('files added')} ";
594 var changed_suffix = " ${_('files changed')} ";
597 var changed_suffix = " ${_('files changed')} ";
595 var removed_suffix = " ${_('files removed')} ";
598 var removed_suffix = " ${_('files removed')} ";
596
599
597
600
598 if(nr_commits == 1){nr_commits_suffix = " ${_('commit')} ";}
601 if(nr_commits == 1){nr_commits_suffix = " ${_('commit')} ";}
599 if(added==1){added_suffix=" ${_('file added')} ";}
602 if(added==1){added_suffix=" ${_('file added')} ";}
600 if(changed==1){changed_suffix=" ${_('file changed')} ";}
603 if(changed==1){changed_suffix=" ${_('file changed')} ";}
601 if(removed==1){removed_suffix=" ${_('file removed')} ";}
604 if(removed==1){removed_suffix=" ${_('file removed')} ";}
602
605
603 showTooltip(item.pageX, item.pageY, item.series.label + " on " + fd
606 showTooltip(item.pageX, item.pageY, item.series.label + " on " + fd
604 +'<br/>'+
607 +'<br/>'+
605 nr_commits + nr_commits_suffix+'<br/>'+
608 nr_commits + nr_commits_suffix+'<br/>'+
606 added + added_suffix +'<br/>'+
609 added + added_suffix +'<br/>'+
607 changed + changed_suffix + '<br/>'+
610 changed + changed_suffix + '<br/>'+
608 removed + removed_suffix + '<br/>');
611 removed + removed_suffix + '<br/>');
609 }
612 }
610 }
613 }
611 else {
614 else {
612 var tooltip = YUD.get("tooltip");
615 var tooltip = YUD.get("tooltip");
613
616
614 if(tooltip) {
617 if(tooltip) {
615 tooltip.parentNode.removeChild(tooltip);
618 tooltip.parentNode.removeChild(tooltip);
616 }
619 }
617 previousPoint = null;
620 previousPoint = null;
618 }
621 }
619 }
622 }
620
623
621 /**
624 /**
622 * MAIN EXECUTION
625 * MAIN EXECUTION
623 */
626 */
624
627
625 var data = getDataAccordingToRanges(initial_ranges);
628 var data = getDataAccordingToRanges(initial_ranges);
626 generateCheckboxes(data);
629 generateCheckboxes(data);
627
630
628 //main plot
631 //main plot
629 var plot = YAHOO.widget.Flot(plotContainer,data,plot_options);
632 var plot = YAHOO.widget.Flot(plotContainer,data,plot_options);
630
633
631 //overview
634 //overview
632 var overview = YAHOO.widget.Flot(overviewContainer, overview_dataset, overview_options);
635 var overview = YAHOO.widget.Flot(overviewContainer, overview_dataset, overview_options);
633
636
634 //show initial selection on overview
637 //show initial selection on overview
635 overview.setSelection(initial_ranges);
638 overview.setSelection(initial_ranges);
636
639
637 plot.subscribe("plotselected", plotselected);
640 plot.subscribe("plotselected", plotselected);
638
641
639 overview.subscribe("plotselected", function (ranges) {
642 overview.subscribe("plotselected", function (ranges) {
640 plot.setSelection(ranges);
643 plot.setSelection(ranges);
641 });
644 });
642
645
643 plot.subscribe("plothover", plothover);
646 plot.subscribe("plothover", plothover);
644
647
645 YUE.on(choiceContainer.getElementsByTagName("input"), "click", plotchoiced, [data, initial_ranges]);
648 YUE.on(choiceContainer.getElementsByTagName("input"), "click", plotchoiced, [data, initial_ranges]);
646 }
649 }
647 SummaryPlot(${c.ts_min},${c.ts_max},${c.commit_data|n},${c.overview_data|n});
650 SummaryPlot(${c.ts_min},${c.ts_max},${c.commit_data|n},${c.overview_data|n});
648 </script>
651 </script>
649
652
650 </div>
653 </div>
651 </div>
654 </div>
652
655
653 <div class="box">
656 <div class="box">
654 <div class="title">
657 <div class="title">
655 <div class="breadcrumbs">${h.link_to(_('Shortlog'),h.url('shortlog_home',repo_name=c.repo_name))}</div>
658 <div class="breadcrumbs">${h.link_to(_('Shortlog'),h.url('shortlog_home',repo_name=c.repo_name))}</div>
656 </div>
659 </div>
657 <div class="table">
660 <div class="table">
658 <div id="shortlog_data">
661 <div id="shortlog_data">
659 <%include file='../shortlog/shortlog_data.html'/>
662 <%include file='../shortlog/shortlog_data.html'/>
660 </div>
663 </div>
661 ##%if c.repo_changesets:
664 ##%if c.repo_changesets:
662 ## ${h.link_to(_('show more'),h.url('changelog_home',repo_name=c.repo_name))}
665 ## ${h.link_to(_('show more'),h.url('changelog_home',repo_name=c.repo_name))}
663 ##%endif
666 ##%endif
664 </div>
667 </div>
665 </div>
668 </div>
666 <div class="box">
669 <div class="box">
667 <div class="title">
670 <div class="title">
668 <div class="breadcrumbs">${h.link_to(_('Tags'),h.url('tags_home',repo_name=c.repo_name))}</div>
671 <div class="breadcrumbs">${h.link_to(_('Tags'),h.url('tags_home',repo_name=c.repo_name))}</div>
669 </div>
672 </div>
670 <div class="table">
673 <div class="table">
671 <%include file='../tags/tags_data.html'/>
674 <%include file='../tags/tags_data.html'/>
672 %if c.repo_changesets:
675 %if c.repo_changesets:
673 ${h.link_to(_('show more'),h.url('tags_home',repo_name=c.repo_name))}
676 ${h.link_to(_('show more'),h.url('tags_home',repo_name=c.repo_name))}
674 %endif
677 %endif
675 </div>
678 </div>
676 </div>
679 </div>
677 <div class="box">
680 <div class="box">
678 <div class="title">
681 <div class="title">
679 <div class="breadcrumbs">${h.link_to(_('Branches'),h.url('branches_home',repo_name=c.repo_name))}</div>
682 <div class="breadcrumbs">${h.link_to(_('Branches'),h.url('branches_home',repo_name=c.repo_name))}</div>
680 </div>
683 </div>
681 <div class="table">
684 <div class="table">
682 <%include file='../branches/branches_data.html'/>
685 <%include file='../branches/branches_data.html'/>
683 %if c.repo_changesets:
686 %if c.repo_changesets:
684 ${h.link_to(_('show more'),h.url('branches_home',repo_name=c.repo_name))}
687 ${h.link_to(_('show more'),h.url('branches_home',repo_name=c.repo_name))}
685 %endif
688 %endif
686 </div>
689 </div>
687 </div>
690 </div>
688
691
689 </%def>
692 </%def>
General Comments 0
You need to be logged in to leave comments. Login now