##// END OF EJS Templates
always use json from compat module
marcink -
r3705:f37d7514 beta
parent child Browse files
Show More
@@ -1,237 +1,244
1 # This program is free software: you can redistribute it and/or modify
1 # This program is free software: you can redistribute it and/or modify
2 # it under the terms of the GNU General Public License as published by
2 # it under the terms of the GNU General Public License as published by
3 # the Free Software Foundation, either version 3 of the License, or
3 # the Free Software Foundation, either version 3 of the License, or
4 # (at your option) any later version.
4 # (at your option) any later version.
5 #
5 #
6 # This program is distributed in the hope that it will be useful,
6 # This program is distributed in the hope that it will be useful,
7 # but WITHOUT ANY WARRANTY; without even the implied warranty of
7 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
8 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # GNU General Public License for more details.
9 # GNU General Public License for more details.
10 #
10 #
11 # You should have received a copy of the GNU General Public License
11 # You should have received a copy of the GNU General Public License
12 # along with this program. If not, see <http://www.gnu.org/licenses/>.
12 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13
13
14 import ldap
14 import ldap
15 import urllib2
15 import urllib2
16 import uuid
16 import uuid
17
18 try:
19 from rhodecode.lib.compat import json
20 except ImportError:
21 try:
22 import simplejson as json
23 except ImportError:
17 import json
24 import json
18
25
19 from ConfigParser import ConfigParser
26 from ConfigParser import ConfigParser
20
27
21 config = ConfigParser()
28 config = ConfigParser()
22 config.read('ldap_sync.conf')
29 config.read('ldap_sync.conf')
23
30
24
31
25 class InvalidResponseIDError(Exception):
32 class InvalidResponseIDError(Exception):
26 """ Request and response don't have the same UUID. """
33 """ Request and response don't have the same UUID. """
27
34
28
35
29 class RhodecodeResponseError(Exception):
36 class RhodecodeResponseError(Exception):
30 """ Response has an error, something went wrong with request execution. """
37 """ Response has an error, something went wrong with request execution. """
31
38
32
39
33 class UserAlreadyInGroupError(Exception):
40 class UserAlreadyInGroupError(Exception):
34 """ User is already a member of the target group. """
41 """ User is already a member of the target group. """
35
42
36
43
37 class UserNotInGroupError(Exception):
44 class UserNotInGroupError(Exception):
38 """ User is not a member of the target group. """
45 """ User is not a member of the target group. """
39
46
40
47
41 class RhodecodeAPI():
48 class RhodecodeAPI():
42
49
43 def __init__(self, url, key):
50 def __init__(self, url, key):
44 self.url = url
51 self.url = url
45 self.key = key
52 self.key = key
46
53
47 def get_api_data(self, uid, method, args):
54 def get_api_data(self, uid, method, args):
48 """Prepare dict for API post."""
55 """Prepare dict for API post."""
49 return {
56 return {
50 "id": uid,
57 "id": uid,
51 "api_key": self.key,
58 "api_key": self.key,
52 "method": method,
59 "method": method,
53 "args": args
60 "args": args
54 }
61 }
55
62
56 def rhodecode_api_post(self, method, args):
63 def rhodecode_api_post(self, method, args):
57 """Send a generic API post to Rhodecode.
64 """Send a generic API post to Rhodecode.
58
65
59 This will generate the UUID for validation check after the
66 This will generate the UUID for validation check after the
60 response is returned. Handle errors and get the result back.
67 response is returned. Handle errors and get the result back.
61 """
68 """
62 uid = str(uuid.uuid1())
69 uid = str(uuid.uuid1())
63 data = self.get_api_data(uid, method, args)
70 data = self.get_api_data(uid, method, args)
64
71
65 data = json.dumps(data)
72 data = json.dumps(data)
66 headers = {'content-type': 'text/plain'}
73 headers = {'content-type': 'text/plain'}
67 req = urllib2.Request(self.url, data, headers)
74 req = urllib2.Request(self.url, data, headers)
68
75
69 response = urllib2.urlopen(req)
76 response = urllib2.urlopen(req)
70 response = json.load(response)
77 response = json.load(response)
71
78
72 if uid != response["id"]:
79 if uid != response["id"]:
73 raise InvalidResponseIDError("UUID does not match.")
80 raise InvalidResponseIDError("UUID does not match.")
74
81
75 if response["error"] != None:
82 if response["error"] != None:
76 raise RhodecodeResponseError(response["error"])
83 raise RhodecodeResponseError(response["error"])
77
84
78 return response["result"]
85 return response["result"]
79
86
80 def create_group(self, name, active=True):
87 def create_group(self, name, active=True):
81 """Create the Rhodecode user group."""
88 """Create the Rhodecode user group."""
82 args = {
89 args = {
83 "group_name": name,
90 "group_name": name,
84 "active": str(active)
91 "active": str(active)
85 }
92 }
86 self.rhodecode_api_post("create_users_group", args)
93 self.rhodecode_api_post("create_users_group", args)
87
94
88 def add_membership(self, group, username):
95 def add_membership(self, group, username):
89 """Add specific user to a group."""
96 """Add specific user to a group."""
90 args = {
97 args = {
91 "usersgroupid": group,
98 "usersgroupid": group,
92 "userid": username
99 "userid": username
93 }
100 }
94 result = self.rhodecode_api_post("add_user_to_users_group", args)
101 result = self.rhodecode_api_post("add_user_to_users_group", args)
95 if not result["success"]:
102 if not result["success"]:
96 raise UserAlreadyInGroupError("User %s already in group %s." %
103 raise UserAlreadyInGroupError("User %s already in group %s." %
97 (username, group))
104 (username, group))
98
105
99 def remove_membership(self, group, username):
106 def remove_membership(self, group, username):
100 """Remove specific user from a group."""
107 """Remove specific user from a group."""
101 args = {
108 args = {
102 "usersgroupid": group,
109 "usersgroupid": group,
103 "userid": username
110 "userid": username
104 }
111 }
105 result = self.rhodecode_api_post("remove_user_from_users_group", args)
112 result = self.rhodecode_api_post("remove_user_from_users_group", args)
106 if not result["success"]:
113 if not result["success"]:
107 raise UserNotInGroupError("User %s not in group %s." %
114 raise UserNotInGroupError("User %s not in group %s." %
108 (username, group))
115 (username, group))
109
116
110 def get_group_members(self, name):
117 def get_group_members(self, name):
111 """Get the list of member usernames from a user group."""
118 """Get the list of member usernames from a user group."""
112 args = {"usersgroupid": name}
119 args = {"usersgroupid": name}
113 members = self.rhodecode_api_post("get_users_group", args)['members']
120 members = self.rhodecode_api_post("get_users_group", args)['members']
114 member_list = []
121 member_list = []
115 for member in members:
122 for member in members:
116 member_list.append(member["username"])
123 member_list.append(member["username"])
117 return member_list
124 return member_list
118
125
119 def get_group(self, name):
126 def get_group(self, name):
120 """Return group info."""
127 """Return group info."""
121 args = {"usersgroupid": name}
128 args = {"usersgroupid": name}
122 return self.rhodecode_api_post("get_users_group", args)
129 return self.rhodecode_api_post("get_users_group", args)
123
130
124 def get_user(self, username):
131 def get_user(self, username):
125 """Return user info."""
132 """Return user info."""
126 args = {"userid": username}
133 args = {"userid": username}
127 return self.rhodecode_api_post("get_user", args)
134 return self.rhodecode_api_post("get_user", args)
128
135
129
136
130 class LdapClient():
137 class LdapClient():
131
138
132 def __init__(self, uri, user, key, base_dn):
139 def __init__(self, uri, user, key, base_dn):
133 self.client = ldap.initialize(uri, trace_level=0)
140 self.client = ldap.initialize(uri, trace_level=0)
134 self.client.set_option(ldap.OPT_REFERRALS, 0)
141 self.client.set_option(ldap.OPT_REFERRALS, 0)
135 self.client.simple_bind(user, key)
142 self.client.simple_bind(user, key)
136 self.base_dn = base_dn
143 self.base_dn = base_dn
137
144
138 def __del__(self):
145 def __del__(self):
139 self.client.unbind()
146 self.client.unbind()
140
147
141 def get_groups(self):
148 def get_groups(self):
142 """Get all the groups in form of dict {group_name: group_info,...}."""
149 """Get all the groups in form of dict {group_name: group_info,...}."""
143 searchFilter = "objectClass=groupOfUniqueNames"
150 searchFilter = "objectClass=groupOfUniqueNames"
144 result = self.client.search_s(self.base_dn, ldap.SCOPE_SUBTREE,
151 result = self.client.search_s(self.base_dn, ldap.SCOPE_SUBTREE,
145 searchFilter)
152 searchFilter)
146
153
147 groups = {}
154 groups = {}
148 for group in result:
155 for group in result:
149 groups[group[1]['cn'][0]] = group[1]
156 groups[group[1]['cn'][0]] = group[1]
150
157
151 return groups
158 return groups
152
159
153 def get_group_users(self, groups, group):
160 def get_group_users(self, groups, group):
154 """Returns all the users belonging to a single group.
161 """Returns all the users belonging to a single group.
155
162
156 Based on the list of groups and memberships, returns all the
163 Based on the list of groups and memberships, returns all the
157 users belonging to a single group, searching recursively.
164 users belonging to a single group, searching recursively.
158 """
165 """
159 users = []
166 users = []
160 for member in groups[group]["uniqueMember"]:
167 for member in groups[group]["uniqueMember"]:
161 member = self.parse_member_string(member)
168 member = self.parse_member_string(member)
162 if member[0] == "uid":
169 if member[0] == "uid":
163 users.append(member[1])
170 users.append(member[1])
164 elif member[0] == "cn":
171 elif member[0] == "cn":
165 users += self.get_group_users(groups, member[1])
172 users += self.get_group_users(groups, member[1])
166
173
167 return users
174 return users
168
175
169 def parse_member_string(self, member):
176 def parse_member_string(self, member):
170 """Parses the member string and returns a touple of type and name.
177 """Parses the member string and returns a touple of type and name.
171
178
172 Unique member can be either user or group. Users will have 'uid' as
179 Unique member can be either user or group. Users will have 'uid' as
173 prefix while groups will have 'cn'.
180 prefix while groups will have 'cn'.
174 """
181 """
175 member = member.split(",")[0]
182 member = member.split(",")[0]
176 return member.split('=')
183 return member.split('=')
177
184
178
185
179 class LdapSync(object):
186 class LdapSync(object):
180
187
181 def __init__(self):
188 def __init__(self):
182 self.ldap_client = LdapClient(config.get("default", "ldap_uri"),
189 self.ldap_client = LdapClient(config.get("default", "ldap_uri"),
183 config.get("default", "ldap_user"),
190 config.get("default", "ldap_user"),
184 config.get("default", "ldap_key"),
191 config.get("default", "ldap_key"),
185 config.get("default", "base_dn"))
192 config.get("default", "base_dn"))
186 self.rhodocode_api = RhodecodeAPI(config.get("default", "api_url"),
193 self.rhodocode_api = RhodecodeAPI(config.get("default", "api_url"),
187 config.get("default", "api_key"))
194 config.get("default", "api_key"))
188
195
189 def update_groups_from_ldap(self):
196 def update_groups_from_ldap(self):
190 """Add all the groups from LDAP to Rhodecode."""
197 """Add all the groups from LDAP to Rhodecode."""
191 added = existing = 0
198 added = existing = 0
192 groups = self.ldap_client.get_groups()
199 groups = self.ldap_client.get_groups()
193 for group in groups:
200 for group in groups:
194 try:
201 try:
195 self.rhodecode_api.create_group(group)
202 self.rhodecode_api.create_group(group)
196 added += 1
203 added += 1
197 except Exception:
204 except Exception:
198 existing += 1
205 existing += 1
199
206
200 return added, existing
207 return added, existing
201
208
202 def update_memberships_from_ldap(self, group):
209 def update_memberships_from_ldap(self, group):
203 """Update memberships in rhodecode based on the LDAP groups."""
210 """Update memberships in rhodecode based on the LDAP groups."""
204 groups = self.ldap_client.get_groups()
211 groups = self.ldap_client.get_groups()
205 group_users = self.ldap_client.get_group_users(groups, group)
212 group_users = self.ldap_client.get_group_users(groups, group)
206
213
207 # Delete memberships first from each group which are not part
214 # Delete memberships first from each group which are not part
208 # of the group any more.
215 # of the group any more.
209 rhodecode_members = self.rhodecode_api.get_group_members(group)
216 rhodecode_members = self.rhodecode_api.get_group_members(group)
210 for rhodecode_member in rhodecode_members:
217 for rhodecode_member in rhodecode_members:
211 if rhodecode_member not in group_users:
218 if rhodecode_member not in group_users:
212 try:
219 try:
213 self.rhodocode_api.remove_membership(group,
220 self.rhodocode_api.remove_membership(group,
214 rhodecode_member)
221 rhodecode_member)
215 except UserNotInGroupError:
222 except UserNotInGroupError:
216 pass
223 pass
217
224
218 # Add memberships.
225 # Add memberships.
219 for member in group_users:
226 for member in group_users:
220 try:
227 try:
221 self.rhodecode_api.add_membership(group, member)
228 self.rhodecode_api.add_membership(group, member)
222 except UserAlreadyInGroupError:
229 except UserAlreadyInGroupError:
223 # TODO: handle somehow maybe..
230 # TODO: handle somehow maybe..
224 pass
231 pass
225
232
226
233
227 if __name__ == '__main__':
234 if __name__ == '__main__':
228 sync = LdapSync()
235 sync = LdapSync()
229 print sync.update_groups_from_ldap()
236 print sync.update_groups_from_ldap()
230
237
231 for gr in sync.ldap_client.get_groups():
238 for gr in sync.ldap_client.get_groups():
232 # TODO: exception when user does not exist during add membership...
239 # TODO: exception when user does not exist during add membership...
233 # How should we handle this.. Either sync users as well at this step,
240 # How should we handle this.. Either sync users as well at this step,
234 # or just ignore those who don't exist. If we want the second case,
241 # or just ignore those who don't exist. If we want the second case,
235 # we need to find a way to recognize the right exception (we always get
242 # we need to find a way to recognize the right exception (we always get
236 # RhodecodeResponseError with no error code so maybe by return msg (?)
243 # RhodecodeResponseError with no error code so maybe by return msg (?)
237 sync.update_memberships_from_ldap(gr)
244 sync.update_memberships_from_ldap(gr)
@@ -1,392 +1,392
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.admin.repos_groups
3 rhodecode.controllers.admin.repos_groups
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Repository groups controller for RhodeCode
6 Repository groups controller for RhodeCode
7
7
8 :created_on: Mar 23, 2010
8 :created_on: Mar 23, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import logging
26 import logging
27 import traceback
27 import traceback
28 import formencode
28 import formencode
29
29
30 from formencode import htmlfill
30 from formencode import htmlfill
31
31
32 from pylons import request, tmpl_context as c, url
32 from pylons import request, tmpl_context as c, url
33 from pylons.controllers.util import abort, redirect
33 from pylons.controllers.util import abort, redirect
34 from pylons.i18n.translation import _
34 from pylons.i18n.translation import _
35
35
36 from sqlalchemy.exc import IntegrityError
36 from sqlalchemy.exc import IntegrityError
37
37
38 import rhodecode
38 import rhodecode
39 from rhodecode.lib import helpers as h
39 from rhodecode.lib import helpers as h
40 from rhodecode.lib.ext_json import json
40 from rhodecode.lib.compat import json
41 from rhodecode.lib.auth import LoginRequired, HasPermissionAnyDecorator,\
41 from rhodecode.lib.auth import LoginRequired, HasPermissionAnyDecorator,\
42 HasReposGroupPermissionAnyDecorator, HasReposGroupPermissionAll,\
42 HasReposGroupPermissionAnyDecorator, HasReposGroupPermissionAll,\
43 HasPermissionAll
43 HasPermissionAll
44 from rhodecode.lib.base import BaseController, render
44 from rhodecode.lib.base import BaseController, render
45 from rhodecode.model.db import RepoGroup, Repository
45 from rhodecode.model.db import RepoGroup, Repository
46 from rhodecode.model.repos_group import ReposGroupModel
46 from rhodecode.model.repos_group import ReposGroupModel
47 from rhodecode.model.forms import ReposGroupForm
47 from rhodecode.model.forms import ReposGroupForm
48 from rhodecode.model.meta import Session
48 from rhodecode.model.meta import Session
49 from rhodecode.model.repo import RepoModel
49 from rhodecode.model.repo import RepoModel
50 from webob.exc import HTTPInternalServerError, HTTPNotFound
50 from webob.exc import HTTPInternalServerError, HTTPNotFound
51 from rhodecode.lib.utils2 import str2bool, safe_int
51 from rhodecode.lib.utils2 import str2bool, safe_int
52 from sqlalchemy.sql.expression import func
52 from sqlalchemy.sql.expression import func
53 from rhodecode.model.scm import GroupList
53 from rhodecode.model.scm import GroupList
54
54
55 log = logging.getLogger(__name__)
55 log = logging.getLogger(__name__)
56
56
57
57
58 class ReposGroupsController(BaseController):
58 class ReposGroupsController(BaseController):
59 """REST Controller styled on the Atom Publishing Protocol"""
59 """REST Controller styled on the Atom Publishing Protocol"""
60 # To properly map this controller, ensure your config/routing.py
60 # To properly map this controller, ensure your config/routing.py
61 # file has a resource setup:
61 # file has a resource setup:
62 # map.resource('repos_group', 'repos_groups')
62 # map.resource('repos_group', 'repos_groups')
63
63
64 @LoginRequired()
64 @LoginRequired()
65 def __before__(self):
65 def __before__(self):
66 super(ReposGroupsController, self).__before__()
66 super(ReposGroupsController, self).__before__()
67
67
68 def __load_defaults(self, allow_empty_group=False, exclude_group_ids=[]):
68 def __load_defaults(self, allow_empty_group=False, exclude_group_ids=[]):
69 if HasPermissionAll('hg.admin')('group edit'):
69 if HasPermissionAll('hg.admin')('group edit'):
70 #we're global admin, we're ok and we can create TOP level groups
70 #we're global admin, we're ok and we can create TOP level groups
71 allow_empty_group = True
71 allow_empty_group = True
72
72
73 #override the choices for this form, we need to filter choices
73 #override the choices for this form, we need to filter choices
74 #and display only those we have ADMIN right
74 #and display only those we have ADMIN right
75 groups_with_admin_rights = GroupList(RepoGroup.query().all(),
75 groups_with_admin_rights = GroupList(RepoGroup.query().all(),
76 perm_set=['group.admin'])
76 perm_set=['group.admin'])
77 c.repo_groups = RepoGroup.groups_choices(groups=groups_with_admin_rights,
77 c.repo_groups = RepoGroup.groups_choices(groups=groups_with_admin_rights,
78 show_empty_group=allow_empty_group)
78 show_empty_group=allow_empty_group)
79 # exclude filtered ids
79 # exclude filtered ids
80 c.repo_groups = filter(lambda x: x[0] not in exclude_group_ids,
80 c.repo_groups = filter(lambda x: x[0] not in exclude_group_ids,
81 c.repo_groups)
81 c.repo_groups)
82 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
82 c.repo_groups_choices = map(lambda k: unicode(k[0]), c.repo_groups)
83 repo_model = RepoModel()
83 repo_model = RepoModel()
84 c.users_array = repo_model.get_users_js()
84 c.users_array = repo_model.get_users_js()
85 c.users_groups_array = repo_model.get_users_groups_js()
85 c.users_groups_array = repo_model.get_users_groups_js()
86
86
87 def __load_data(self, group_id):
87 def __load_data(self, group_id):
88 """
88 """
89 Load defaults settings for edit, and update
89 Load defaults settings for edit, and update
90
90
91 :param group_id:
91 :param group_id:
92 """
92 """
93 repo_group = RepoGroup.get_or_404(group_id)
93 repo_group = RepoGroup.get_or_404(group_id)
94 data = repo_group.get_dict()
94 data = repo_group.get_dict()
95 data['group_name'] = repo_group.name
95 data['group_name'] = repo_group.name
96
96
97 # fill repository users
97 # fill repository users
98 for p in repo_group.repo_group_to_perm:
98 for p in repo_group.repo_group_to_perm:
99 data.update({'u_perm_%s' % p.user.username:
99 data.update({'u_perm_%s' % p.user.username:
100 p.permission.permission_name})
100 p.permission.permission_name})
101
101
102 # fill repository groups
102 # fill repository groups
103 for p in repo_group.users_group_to_perm:
103 for p in repo_group.users_group_to_perm:
104 data.update({'g_perm_%s' % p.users_group.users_group_name:
104 data.update({'g_perm_%s' % p.users_group.users_group_name:
105 p.permission.permission_name})
105 p.permission.permission_name})
106
106
107 return data
107 return data
108
108
109 def _revoke_perms_on_yourself(self, form_result):
109 def _revoke_perms_on_yourself(self, form_result):
110 _up = filter(lambda u: c.rhodecode_user.username == u[0],
110 _up = filter(lambda u: c.rhodecode_user.username == u[0],
111 form_result['perms_updates'])
111 form_result['perms_updates'])
112 _new = filter(lambda u: c.rhodecode_user.username == u[0],
112 _new = filter(lambda u: c.rhodecode_user.username == u[0],
113 form_result['perms_new'])
113 form_result['perms_new'])
114 if _new and _new[0][1] != 'group.admin' or _up and _up[0][1] != 'group.admin':
114 if _new and _new[0][1] != 'group.admin' or _up and _up[0][1] != 'group.admin':
115 return True
115 return True
116 return False
116 return False
117
117
118 def index(self, format='html'):
118 def index(self, format='html'):
119 """GET /repos_groups: All items in the collection"""
119 """GET /repos_groups: All items in the collection"""
120 # url('repos_groups')
120 # url('repos_groups')
121 group_iter = GroupList(RepoGroup.query().all(), perm_set=['group.admin'])
121 group_iter = GroupList(RepoGroup.query().all(), perm_set=['group.admin'])
122 sk = lambda g: g.parents[0].group_name if g.parents else g.group_name
122 sk = lambda g: g.parents[0].group_name if g.parents else g.group_name
123 c.groups = sorted(group_iter, key=sk)
123 c.groups = sorted(group_iter, key=sk)
124 return render('admin/repos_groups/repos_groups_show.html')
124 return render('admin/repos_groups/repos_groups_show.html')
125
125
126 def create(self):
126 def create(self):
127 """POST /repos_groups: Create a new item"""
127 """POST /repos_groups: Create a new item"""
128 # url('repos_groups')
128 # url('repos_groups')
129
129
130 self.__load_defaults()
130 self.__load_defaults()
131
131
132 # permissions for can create group based on parent_id are checked
132 # permissions for can create group based on parent_id are checked
133 # here in the Form
133 # here in the Form
134 repos_group_form = ReposGroupForm(available_groups=
134 repos_group_form = ReposGroupForm(available_groups=
135 map(lambda k: unicode(k[0]), c.repo_groups))()
135 map(lambda k: unicode(k[0]), c.repo_groups))()
136 try:
136 try:
137 form_result = repos_group_form.to_python(dict(request.POST))
137 form_result = repos_group_form.to_python(dict(request.POST))
138 ReposGroupModel().create(
138 ReposGroupModel().create(
139 group_name=form_result['group_name'],
139 group_name=form_result['group_name'],
140 group_description=form_result['group_description'],
140 group_description=form_result['group_description'],
141 parent=form_result['group_parent_id'],
141 parent=form_result['group_parent_id'],
142 owner=self.rhodecode_user.user_id
142 owner=self.rhodecode_user.user_id
143 )
143 )
144 Session().commit()
144 Session().commit()
145 h.flash(_('Created repository group %s') \
145 h.flash(_('Created repository group %s') \
146 % form_result['group_name'], category='success')
146 % form_result['group_name'], category='success')
147 #TODO: in futureaction_logger(, '', '', '', self.sa)
147 #TODO: in futureaction_logger(, '', '', '', self.sa)
148 except formencode.Invalid, errors:
148 except formencode.Invalid, errors:
149 return htmlfill.render(
149 return htmlfill.render(
150 render('admin/repos_groups/repos_groups_add.html'),
150 render('admin/repos_groups/repos_groups_add.html'),
151 defaults=errors.value,
151 defaults=errors.value,
152 errors=errors.error_dict or {},
152 errors=errors.error_dict or {},
153 prefix_error=False,
153 prefix_error=False,
154 encoding="UTF-8")
154 encoding="UTF-8")
155 except Exception:
155 except Exception:
156 log.error(traceback.format_exc())
156 log.error(traceback.format_exc())
157 h.flash(_('Error occurred during creation of repository group %s') \
157 h.flash(_('Error occurred during creation of repository group %s') \
158 % request.POST.get('group_name'), category='error')
158 % request.POST.get('group_name'), category='error')
159 parent_group_id = form_result['group_parent_id']
159 parent_group_id = form_result['group_parent_id']
160 #TODO: maybe we should get back to the main view, not the admin one
160 #TODO: maybe we should get back to the main view, not the admin one
161 return redirect(url('repos_groups', parent_group=parent_group_id))
161 return redirect(url('repos_groups', parent_group=parent_group_id))
162
162
163 def new(self, format='html'):
163 def new(self, format='html'):
164 """GET /repos_groups/new: Form to create a new item"""
164 """GET /repos_groups/new: Form to create a new item"""
165 # url('new_repos_group')
165 # url('new_repos_group')
166 if HasPermissionAll('hg.admin')('group create'):
166 if HasPermissionAll('hg.admin')('group create'):
167 #we're global admin, we're ok and we can create TOP level groups
167 #we're global admin, we're ok and we can create TOP level groups
168 pass
168 pass
169 else:
169 else:
170 # we pass in parent group into creation form, thus we know
170 # we pass in parent group into creation form, thus we know
171 # what would be the group, we can check perms here !
171 # what would be the group, we can check perms here !
172 group_id = safe_int(request.GET.get('parent_group'))
172 group_id = safe_int(request.GET.get('parent_group'))
173 group = RepoGroup.get(group_id) if group_id else None
173 group = RepoGroup.get(group_id) if group_id else None
174 group_name = group.group_name if group else None
174 group_name = group.group_name if group else None
175 if HasReposGroupPermissionAll('group.admin')(group_name, 'group create'):
175 if HasReposGroupPermissionAll('group.admin')(group_name, 'group create'):
176 pass
176 pass
177 else:
177 else:
178 return abort(403)
178 return abort(403)
179
179
180 self.__load_defaults()
180 self.__load_defaults()
181 return render('admin/repos_groups/repos_groups_add.html')
181 return render('admin/repos_groups/repos_groups_add.html')
182
182
183 @HasReposGroupPermissionAnyDecorator('group.admin')
183 @HasReposGroupPermissionAnyDecorator('group.admin')
184 def update(self, group_name):
184 def update(self, group_name):
185 """PUT /repos_groups/group_name: Update an existing item"""
185 """PUT /repos_groups/group_name: Update an existing item"""
186 # Forms posted to this method should contain a hidden field:
186 # Forms posted to this method should contain a hidden field:
187 # <input type="hidden" name="_method" value="PUT" />
187 # <input type="hidden" name="_method" value="PUT" />
188 # Or using helpers:
188 # Or using helpers:
189 # h.form(url('repos_group', group_name=GROUP_NAME),
189 # h.form(url('repos_group', group_name=GROUP_NAME),
190 # method='put')
190 # method='put')
191 # url('repos_group', group_name=GROUP_NAME)
191 # url('repos_group', group_name=GROUP_NAME)
192
192
193 c.repos_group = ReposGroupModel()._get_repos_group(group_name)
193 c.repos_group = ReposGroupModel()._get_repos_group(group_name)
194 if HasPermissionAll('hg.admin')('group edit'):
194 if HasPermissionAll('hg.admin')('group edit'):
195 #we're global admin, we're ok and we can create TOP level groups
195 #we're global admin, we're ok and we can create TOP level groups
196 allow_empty_group = True
196 allow_empty_group = True
197 elif not c.repos_group.parent_group:
197 elif not c.repos_group.parent_group:
198 allow_empty_group = True
198 allow_empty_group = True
199 else:
199 else:
200 allow_empty_group = False
200 allow_empty_group = False
201 self.__load_defaults(allow_empty_group=allow_empty_group,
201 self.__load_defaults(allow_empty_group=allow_empty_group,
202 exclude_group_ids=[c.repos_group.group_id])
202 exclude_group_ids=[c.repos_group.group_id])
203
203
204 repos_group_form = ReposGroupForm(
204 repos_group_form = ReposGroupForm(
205 edit=True,
205 edit=True,
206 old_data=c.repos_group.get_dict(),
206 old_data=c.repos_group.get_dict(),
207 available_groups=c.repo_groups_choices,
207 available_groups=c.repo_groups_choices,
208 can_create_in_root=allow_empty_group,
208 can_create_in_root=allow_empty_group,
209 )()
209 )()
210 try:
210 try:
211 form_result = repos_group_form.to_python(dict(request.POST))
211 form_result = repos_group_form.to_python(dict(request.POST))
212 if not c.rhodecode_user.is_admin:
212 if not c.rhodecode_user.is_admin:
213 if self._revoke_perms_on_yourself(form_result):
213 if self._revoke_perms_on_yourself(form_result):
214 msg = _('Cannot revoke permission for yourself as admin')
214 msg = _('Cannot revoke permission for yourself as admin')
215 h.flash(msg, category='warning')
215 h.flash(msg, category='warning')
216 raise Exception('revoke admin permission on self')
216 raise Exception('revoke admin permission on self')
217
217
218 new_gr = ReposGroupModel().update(group_name, form_result)
218 new_gr = ReposGroupModel().update(group_name, form_result)
219 Session().commit()
219 Session().commit()
220 h.flash(_('Updated repository group %s') \
220 h.flash(_('Updated repository group %s') \
221 % form_result['group_name'], category='success')
221 % form_result['group_name'], category='success')
222 # we now have new name !
222 # we now have new name !
223 group_name = new_gr.group_name
223 group_name = new_gr.group_name
224 #TODO: in future action_logger(, '', '', '', self.sa)
224 #TODO: in future action_logger(, '', '', '', self.sa)
225 except formencode.Invalid, errors:
225 except formencode.Invalid, errors:
226
226
227 return htmlfill.render(
227 return htmlfill.render(
228 render('admin/repos_groups/repos_groups_edit.html'),
228 render('admin/repos_groups/repos_groups_edit.html'),
229 defaults=errors.value,
229 defaults=errors.value,
230 errors=errors.error_dict or {},
230 errors=errors.error_dict or {},
231 prefix_error=False,
231 prefix_error=False,
232 encoding="UTF-8")
232 encoding="UTF-8")
233 except Exception:
233 except Exception:
234 log.error(traceback.format_exc())
234 log.error(traceback.format_exc())
235 h.flash(_('Error occurred during update of repository group %s') \
235 h.flash(_('Error occurred during update of repository group %s') \
236 % request.POST.get('group_name'), category='error')
236 % request.POST.get('group_name'), category='error')
237
237
238 return redirect(url('edit_repos_group', group_name=group_name))
238 return redirect(url('edit_repos_group', group_name=group_name))
239
239
240 @HasReposGroupPermissionAnyDecorator('group.admin')
240 @HasReposGroupPermissionAnyDecorator('group.admin')
241 def delete(self, group_name):
241 def delete(self, group_name):
242 """DELETE /repos_groups/group_name: Delete an existing item"""
242 """DELETE /repos_groups/group_name: Delete an existing item"""
243 # Forms posted to this method should contain a hidden field:
243 # Forms posted to this method should contain a hidden field:
244 # <input type="hidden" name="_method" value="DELETE" />
244 # <input type="hidden" name="_method" value="DELETE" />
245 # Or using helpers:
245 # Or using helpers:
246 # h.form(url('repos_group', group_name=GROUP_NAME),
246 # h.form(url('repos_group', group_name=GROUP_NAME),
247 # method='delete')
247 # method='delete')
248 # url('repos_group', group_name=GROUP_NAME)
248 # url('repos_group', group_name=GROUP_NAME)
249
249
250 gr = c.repos_group = ReposGroupModel()._get_repos_group(group_name)
250 gr = c.repos_group = ReposGroupModel()._get_repos_group(group_name)
251 repos = gr.repositories.all()
251 repos = gr.repositories.all()
252 if repos:
252 if repos:
253 h.flash(_('This group contains %s repositores and cannot be '
253 h.flash(_('This group contains %s repositores and cannot be '
254 'deleted') % len(repos), category='warning')
254 'deleted') % len(repos), category='warning')
255 return redirect(url('repos_groups'))
255 return redirect(url('repos_groups'))
256
256
257 children = gr.children.all()
257 children = gr.children.all()
258 if children:
258 if children:
259 h.flash(_('This group contains %s subgroups and cannot be deleted'
259 h.flash(_('This group contains %s subgroups and cannot be deleted'
260 % (len(children))), category='warning')
260 % (len(children))), category='warning')
261 return redirect(url('repos_groups'))
261 return redirect(url('repos_groups'))
262
262
263 try:
263 try:
264 ReposGroupModel().delete(group_name)
264 ReposGroupModel().delete(group_name)
265 Session().commit()
265 Session().commit()
266 h.flash(_('Removed repository group %s') % group_name,
266 h.flash(_('Removed repository group %s') % group_name,
267 category='success')
267 category='success')
268 #TODO: in future action_logger(, '', '', '', self.sa)
268 #TODO: in future action_logger(, '', '', '', self.sa)
269 except Exception:
269 except Exception:
270 log.error(traceback.format_exc())
270 log.error(traceback.format_exc())
271 h.flash(_('Error occurred during deletion of repos '
271 h.flash(_('Error occurred during deletion of repos '
272 'group %s') % group_name, category='error')
272 'group %s') % group_name, category='error')
273
273
274 return redirect(url('repos_groups'))
274 return redirect(url('repos_groups'))
275
275
276 @HasReposGroupPermissionAnyDecorator('group.admin')
276 @HasReposGroupPermissionAnyDecorator('group.admin')
277 def delete_repos_group_user_perm(self, group_name):
277 def delete_repos_group_user_perm(self, group_name):
278 """
278 """
279 DELETE an existing repository group permission user
279 DELETE an existing repository group permission user
280
280
281 :param group_name:
281 :param group_name:
282 """
282 """
283 try:
283 try:
284 if not c.rhodecode_user.is_admin:
284 if not c.rhodecode_user.is_admin:
285 if c.rhodecode_user.user_id == safe_int(request.POST['user_id']):
285 if c.rhodecode_user.user_id == safe_int(request.POST['user_id']):
286 msg = _('Cannot revoke permission for yourself as admin')
286 msg = _('Cannot revoke permission for yourself as admin')
287 h.flash(msg, category='warning')
287 h.flash(msg, category='warning')
288 raise Exception('revoke admin permission on self')
288 raise Exception('revoke admin permission on self')
289 recursive = str2bool(request.POST.get('recursive', False))
289 recursive = str2bool(request.POST.get('recursive', False))
290 ReposGroupModel().delete_permission(
290 ReposGroupModel().delete_permission(
291 repos_group=group_name, obj=request.POST['user_id'],
291 repos_group=group_name, obj=request.POST['user_id'],
292 obj_type='user', recursive=recursive
292 obj_type='user', recursive=recursive
293 )
293 )
294 Session().commit()
294 Session().commit()
295 except Exception:
295 except Exception:
296 log.error(traceback.format_exc())
296 log.error(traceback.format_exc())
297 h.flash(_('An error occurred during deletion of group user'),
297 h.flash(_('An error occurred during deletion of group user'),
298 category='error')
298 category='error')
299 raise HTTPInternalServerError()
299 raise HTTPInternalServerError()
300
300
301 @HasReposGroupPermissionAnyDecorator('group.admin')
301 @HasReposGroupPermissionAnyDecorator('group.admin')
302 def delete_repos_group_users_group_perm(self, group_name):
302 def delete_repos_group_users_group_perm(self, group_name):
303 """
303 """
304 DELETE an existing repository group permission user group
304 DELETE an existing repository group permission user group
305
305
306 :param group_name:
306 :param group_name:
307 """
307 """
308
308
309 try:
309 try:
310 recursive = str2bool(request.POST.get('recursive', False))
310 recursive = str2bool(request.POST.get('recursive', False))
311 ReposGroupModel().delete_permission(
311 ReposGroupModel().delete_permission(
312 repos_group=group_name, obj=request.POST['users_group_id'],
312 repos_group=group_name, obj=request.POST['users_group_id'],
313 obj_type='users_group', recursive=recursive
313 obj_type='users_group', recursive=recursive
314 )
314 )
315 Session().commit()
315 Session().commit()
316 except Exception:
316 except Exception:
317 log.error(traceback.format_exc())
317 log.error(traceback.format_exc())
318 h.flash(_('An error occurred during deletion of group'
318 h.flash(_('An error occurred during deletion of group'
319 ' user groups'),
319 ' user groups'),
320 category='error')
320 category='error')
321 raise HTTPInternalServerError()
321 raise HTTPInternalServerError()
322
322
323 def show_by_name(self, group_name):
323 def show_by_name(self, group_name):
324 """
324 """
325 This is a proxy that does a lookup group_name -> id, and shows
325 This is a proxy that does a lookup group_name -> id, and shows
326 the group by id view instead
326 the group by id view instead
327 """
327 """
328 group_name = group_name.rstrip('/')
328 group_name = group_name.rstrip('/')
329 id_ = RepoGroup.get_by_group_name(group_name)
329 id_ = RepoGroup.get_by_group_name(group_name)
330 if id_:
330 if id_:
331 return self.show(id_.group_id)
331 return self.show(id_.group_id)
332 raise HTTPNotFound
332 raise HTTPNotFound
333
333
334 @HasReposGroupPermissionAnyDecorator('group.read', 'group.write',
334 @HasReposGroupPermissionAnyDecorator('group.read', 'group.write',
335 'group.admin')
335 'group.admin')
336 def show(self, group_name, format='html'):
336 def show(self, group_name, format='html'):
337 """GET /repos_groups/group_name: Show a specific item"""
337 """GET /repos_groups/group_name: Show a specific item"""
338 # url('repos_group', group_name=GROUP_NAME)
338 # url('repos_group', group_name=GROUP_NAME)
339
339
340 c.group = c.repos_group = ReposGroupModel()._get_repos_group(group_name)
340 c.group = c.repos_group = ReposGroupModel()._get_repos_group(group_name)
341 c.group_repos = c.group.repositories.all()
341 c.group_repos = c.group.repositories.all()
342
342
343 #overwrite our cached list with current filter
343 #overwrite our cached list with current filter
344 gr_filter = c.group_repos
344 gr_filter = c.group_repos
345 c.repo_cnt = 0
345 c.repo_cnt = 0
346
346
347 groups = RepoGroup.query().order_by(RepoGroup.group_name)\
347 groups = RepoGroup.query().order_by(RepoGroup.group_name)\
348 .filter(RepoGroup.group_parent_id == c.group.group_id).all()
348 .filter(RepoGroup.group_parent_id == c.group.group_id).all()
349 c.groups = self.scm_model.get_repos_groups(groups)
349 c.groups = self.scm_model.get_repos_groups(groups)
350
350
351 if not c.visual.lightweight_dashboard:
351 if not c.visual.lightweight_dashboard:
352 c.repos_list = self.scm_model.get_repos(all_repos=gr_filter)
352 c.repos_list = self.scm_model.get_repos(all_repos=gr_filter)
353 ## lightweight version of dashboard
353 ## lightweight version of dashboard
354 else:
354 else:
355 c.repos_list = Repository.query()\
355 c.repos_list = Repository.query()\
356 .filter(Repository.group_id == c.group.group_id)\
356 .filter(Repository.group_id == c.group.group_id)\
357 .order_by(func.lower(Repository.repo_name))\
357 .order_by(func.lower(Repository.repo_name))\
358 .all()
358 .all()
359
359
360 repos_data = RepoModel().get_repos_as_dict(repos_list=c.repos_list,
360 repos_data = RepoModel().get_repos_as_dict(repos_list=c.repos_list,
361 admin=False)
361 admin=False)
362 #json used to render the grid
362 #json used to render the grid
363 c.data = json.dumps(repos_data)
363 c.data = json.dumps(repos_data)
364
364
365 return render('admin/repos_groups/repos_groups.html')
365 return render('admin/repos_groups/repos_groups.html')
366
366
367 @HasReposGroupPermissionAnyDecorator('group.admin')
367 @HasReposGroupPermissionAnyDecorator('group.admin')
368 def edit(self, group_name, format='html'):
368 def edit(self, group_name, format='html'):
369 """GET /repos_groups/group_name/edit: Form to edit an existing item"""
369 """GET /repos_groups/group_name/edit: Form to edit an existing item"""
370 # url('edit_repos_group', group_name=GROUP_NAME)
370 # url('edit_repos_group', group_name=GROUP_NAME)
371
371
372 c.repos_group = ReposGroupModel()._get_repos_group(group_name)
372 c.repos_group = ReposGroupModel()._get_repos_group(group_name)
373 #we can only allow moving empty group if it's already a top-level
373 #we can only allow moving empty group if it's already a top-level
374 #group, ie has no parents, or we're admin
374 #group, ie has no parents, or we're admin
375 if HasPermissionAll('hg.admin')('group edit'):
375 if HasPermissionAll('hg.admin')('group edit'):
376 #we're global admin, we're ok and we can create TOP level groups
376 #we're global admin, we're ok and we can create TOP level groups
377 allow_empty_group = True
377 allow_empty_group = True
378 elif not c.repos_group.parent_group:
378 elif not c.repos_group.parent_group:
379 allow_empty_group = True
379 allow_empty_group = True
380 else:
380 else:
381 allow_empty_group = False
381 allow_empty_group = False
382
382
383 self.__load_defaults(allow_empty_group=allow_empty_group,
383 self.__load_defaults(allow_empty_group=allow_empty_group,
384 exclude_group_ids=[c.repos_group.group_id])
384 exclude_group_ids=[c.repos_group.group_id])
385 defaults = self.__load_data(c.repos_group.group_id)
385 defaults = self.__load_data(c.repos_group.group_id)
386
386
387 return htmlfill.render(
387 return htmlfill.render(
388 render('admin/repos_groups/repos_groups_edit.html'),
388 render('admin/repos_groups/repos_groups_edit.html'),
389 defaults=defaults,
389 defaults=defaults,
390 encoding="UTF-8",
390 encoding="UTF-8",
391 force_defaults=False
391 force_defaults=False
392 )
392 )
@@ -1,87 +1,87
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.home
3 rhodecode.controllers.home
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 Home controller for Rhodecode
6 Home controller for Rhodecode
7
7
8 :created_on: Feb 18, 2010
8 :created_on: Feb 18, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import logging
26 import logging
27
27
28 from pylons import tmpl_context as c, request
28 from pylons import tmpl_context as c, request
29 from pylons.i18n.translation import _
29 from pylons.i18n.translation import _
30 from webob.exc import HTTPBadRequest
30 from webob.exc import HTTPBadRequest
31 from sqlalchemy.sql.expression import func
31 from sqlalchemy.sql.expression import func
32
32
33 import rhodecode
33 import rhodecode
34 from rhodecode.lib import helpers as h
34 from rhodecode.lib import helpers as h
35 from rhodecode.lib.ext_json import json
35 from rhodecode.lib.compat import json
36 from rhodecode.lib.auth import LoginRequired
36 from rhodecode.lib.auth import LoginRequired
37 from rhodecode.lib.base import BaseController, render
37 from rhodecode.lib.base import BaseController, render
38 from rhodecode.model.db import Repository
38 from rhodecode.model.db import Repository
39 from rhodecode.model.repo import RepoModel
39 from rhodecode.model.repo import RepoModel
40
40
41
41
42 log = logging.getLogger(__name__)
42 log = logging.getLogger(__name__)
43
43
44
44
45 class HomeController(BaseController):
45 class HomeController(BaseController):
46
46
47 @LoginRequired()
47 @LoginRequired()
48 def __before__(self):
48 def __before__(self):
49 super(HomeController, self).__before__()
49 super(HomeController, self).__before__()
50
50
51 def index(self):
51 def index(self):
52 c.groups = self.scm_model.get_repos_groups()
52 c.groups = self.scm_model.get_repos_groups()
53 c.group = None
53 c.group = None
54
54
55 if not c.visual.lightweight_dashboard:
55 if not c.visual.lightweight_dashboard:
56 c.repos_list = self.scm_model.get_repos()
56 c.repos_list = self.scm_model.get_repos()
57 ## lightweight version of dashboard
57 ## lightweight version of dashboard
58 else:
58 else:
59 c.repos_list = Repository.query()\
59 c.repos_list = Repository.query()\
60 .filter(Repository.group_id == None)\
60 .filter(Repository.group_id == None)\
61 .order_by(func.lower(Repository.repo_name))\
61 .order_by(func.lower(Repository.repo_name))\
62 .all()
62 .all()
63
63
64 repos_data = RepoModel().get_repos_as_dict(repos_list=c.repos_list,
64 repos_data = RepoModel().get_repos_as_dict(repos_list=c.repos_list,
65 admin=False)
65 admin=False)
66 #json used to render the grid
66 #json used to render the grid
67 c.data = json.dumps(repos_data)
67 c.data = json.dumps(repos_data)
68
68
69 return render('/index.html')
69 return render('/index.html')
70
70
71 def repo_switcher(self):
71 def repo_switcher(self):
72 if request.is_xhr:
72 if request.is_xhr:
73 all_repos = Repository.query().order_by(Repository.repo_name).all()
73 all_repos = Repository.query().order_by(Repository.repo_name).all()
74 c.repos_list = self.scm_model.get_repos(all_repos,
74 c.repos_list = self.scm_model.get_repos(all_repos,
75 sort_key='name_sort',
75 sort_key='name_sort',
76 simple=True)
76 simple=True)
77 return render('/repo_switcher_list.html')
77 return render('/repo_switcher_list.html')
78 else:
78 else:
79 raise HTTPBadRequest()
79 raise HTTPBadRequest()
80
80
81 def branch_tag_switcher(self, repo_name):
81 def branch_tag_switcher(self, repo_name):
82 if request.is_xhr:
82 if request.is_xhr:
83 c.rhodecode_db_repo = Repository.get_by_repo_name(c.repo_name)
83 c.rhodecode_db_repo = Repository.get_by_repo_name(c.repo_name)
84 if c.rhodecode_db_repo:
84 if c.rhodecode_db_repo:
85 c.rhodecode_repo = c.rhodecode_db_repo.scm_instance
85 c.rhodecode_repo = c.rhodecode_db_repo.scm_instance
86 return render('/switch_to_list.html')
86 return render('/switch_to_list.html')
87 raise HTTPBadRequest()
87 raise HTTPBadRequest()
@@ -1,779 +1,779
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.lib.utils
3 rhodecode.lib.utils
4 ~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~
5
5
6 Utilities library for RhodeCode
6 Utilities library for RhodeCode
7
7
8 :created_on: Apr 18, 2010
8 :created_on: Apr 18, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import os
26 import os
27 import re
27 import re
28 import logging
28 import logging
29 import datetime
29 import datetime
30 import traceback
30 import traceback
31 import paste
31 import paste
32 import beaker
32 import beaker
33 import tarfile
33 import tarfile
34 import shutil
34 import shutil
35 import decorator
35 import decorator
36 import warnings
36 import warnings
37 from os.path import abspath
37 from os.path import abspath
38 from os.path import dirname as dn, join as jn
38 from os.path import dirname as dn, join as jn
39
39
40 from paste.script.command import Command, BadCommand
40 from paste.script.command import Command, BadCommand
41
41
42 from mercurial import ui, config
42 from mercurial import ui, config
43
43
44 from webhelpers.text import collapse, remove_formatting, strip_tags
44 from webhelpers.text import collapse, remove_formatting, strip_tags
45
45
46 from rhodecode.lib.vcs import get_backend
46 from rhodecode.lib.vcs import get_backend
47 from rhodecode.lib.vcs.backends.base import BaseChangeset
47 from rhodecode.lib.vcs.backends.base import BaseChangeset
48 from rhodecode.lib.vcs.utils.lazy import LazyProperty
48 from rhodecode.lib.vcs.utils.lazy import LazyProperty
49 from rhodecode.lib.vcs.utils.helpers import get_scm
49 from rhodecode.lib.vcs.utils.helpers import get_scm
50 from rhodecode.lib.vcs.exceptions import VCSError
50 from rhodecode.lib.vcs.exceptions import VCSError
51
51
52 from rhodecode.lib.caching_query import FromCache
52 from rhodecode.lib.caching_query import FromCache
53
53
54 from rhodecode.model import meta
54 from rhodecode.model import meta
55 from rhodecode.model.db import Repository, User, RhodeCodeUi, \
55 from rhodecode.model.db import Repository, User, RhodeCodeUi, \
56 UserLog, RepoGroup, RhodeCodeSetting, CacheInvalidation
56 UserLog, RepoGroup, RhodeCodeSetting, CacheInvalidation
57 from rhodecode.model.meta import Session
57 from rhodecode.model.meta import Session
58 from rhodecode.model.repos_group import ReposGroupModel
58 from rhodecode.model.repos_group import ReposGroupModel
59 from rhodecode.lib.utils2 import safe_str, safe_unicode
59 from rhodecode.lib.utils2 import safe_str, safe_unicode
60 from rhodecode.lib.vcs.utils.fakemod import create_module
60 from rhodecode.lib.vcs.utils.fakemod import create_module
61
61
62 log = logging.getLogger(__name__)
62 log = logging.getLogger(__name__)
63
63
64 REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}__.*')
64 REMOVED_REPO_PAT = re.compile(r'rm__\d{8}_\d{6}_\d{6}__.*')
65
65
66
66
67 def recursive_replace(str_, replace=' '):
67 def recursive_replace(str_, replace=' '):
68 """
68 """
69 Recursive replace of given sign to just one instance
69 Recursive replace of given sign to just one instance
70
70
71 :param str_: given string
71 :param str_: given string
72 :param replace: char to find and replace multiple instances
72 :param replace: char to find and replace multiple instances
73
73
74 Examples::
74 Examples::
75 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
75 >>> recursive_replace("Mighty---Mighty-Bo--sstones",'-')
76 'Mighty-Mighty-Bo-sstones'
76 'Mighty-Mighty-Bo-sstones'
77 """
77 """
78
78
79 if str_.find(replace * 2) == -1:
79 if str_.find(replace * 2) == -1:
80 return str_
80 return str_
81 else:
81 else:
82 str_ = str_.replace(replace * 2, replace)
82 str_ = str_.replace(replace * 2, replace)
83 return recursive_replace(str_, replace)
83 return recursive_replace(str_, replace)
84
84
85
85
86 def repo_name_slug(value):
86 def repo_name_slug(value):
87 """
87 """
88 Return slug of name of repository
88 Return slug of name of repository
89 This function is called on each creation/modification
89 This function is called on each creation/modification
90 of repository to prevent bad names in repo
90 of repository to prevent bad names in repo
91 """
91 """
92
92
93 slug = remove_formatting(value)
93 slug = remove_formatting(value)
94 slug = strip_tags(slug)
94 slug = strip_tags(slug)
95
95
96 for c in """`?=[]\;'"<>,/~!@#$%^&*()+{}|: """:
96 for c in """`?=[]\;'"<>,/~!@#$%^&*()+{}|: """:
97 slug = slug.replace(c, '-')
97 slug = slug.replace(c, '-')
98 slug = recursive_replace(slug, '-')
98 slug = recursive_replace(slug, '-')
99 slug = collapse(slug, '-')
99 slug = collapse(slug, '-')
100 return slug
100 return slug
101
101
102
102
103 def get_repo_slug(request):
103 def get_repo_slug(request):
104 _repo = request.environ['pylons.routes_dict'].get('repo_name')
104 _repo = request.environ['pylons.routes_dict'].get('repo_name')
105 if _repo:
105 if _repo:
106 _repo = _repo.rstrip('/')
106 _repo = _repo.rstrip('/')
107 return _repo
107 return _repo
108
108
109
109
110 def get_repos_group_slug(request):
110 def get_repos_group_slug(request):
111 _group = request.environ['pylons.routes_dict'].get('group_name')
111 _group = request.environ['pylons.routes_dict'].get('group_name')
112 if _group:
112 if _group:
113 _group = _group.rstrip('/')
113 _group = _group.rstrip('/')
114 return _group
114 return _group
115
115
116
116
117 def action_logger(user, action, repo, ipaddr='', sa=None, commit=False):
117 def action_logger(user, action, repo, ipaddr='', sa=None, commit=False):
118 """
118 """
119 Action logger for various actions made by users
119 Action logger for various actions made by users
120
120
121 :param user: user that made this action, can be a unique username string or
121 :param user: user that made this action, can be a unique username string or
122 object containing user_id attribute
122 object containing user_id attribute
123 :param action: action to log, should be on of predefined unique actions for
123 :param action: action to log, should be on of predefined unique actions for
124 easy translations
124 easy translations
125 :param repo: string name of repository or object containing repo_id,
125 :param repo: string name of repository or object containing repo_id,
126 that action was made on
126 that action was made on
127 :param ipaddr: optional ip address from what the action was made
127 :param ipaddr: optional ip address from what the action was made
128 :param sa: optional sqlalchemy session
128 :param sa: optional sqlalchemy session
129
129
130 """
130 """
131
131
132 if not sa:
132 if not sa:
133 sa = meta.Session()
133 sa = meta.Session()
134
134
135 try:
135 try:
136 if hasattr(user, 'user_id'):
136 if hasattr(user, 'user_id'):
137 user_obj = User.get(user.user_id)
137 user_obj = User.get(user.user_id)
138 elif isinstance(user, basestring):
138 elif isinstance(user, basestring):
139 user_obj = User.get_by_username(user)
139 user_obj = User.get_by_username(user)
140 else:
140 else:
141 raise Exception('You have to provide a user object or a username')
141 raise Exception('You have to provide a user object or a username')
142
142
143 if hasattr(repo, 'repo_id'):
143 if hasattr(repo, 'repo_id'):
144 repo_obj = Repository.get(repo.repo_id)
144 repo_obj = Repository.get(repo.repo_id)
145 repo_name = repo_obj.repo_name
145 repo_name = repo_obj.repo_name
146 elif isinstance(repo, basestring):
146 elif isinstance(repo, basestring):
147 repo_name = repo.lstrip('/')
147 repo_name = repo.lstrip('/')
148 repo_obj = Repository.get_by_repo_name(repo_name)
148 repo_obj = Repository.get_by_repo_name(repo_name)
149 else:
149 else:
150 repo_obj = None
150 repo_obj = None
151 repo_name = ''
151 repo_name = ''
152
152
153 user_log = UserLog()
153 user_log = UserLog()
154 user_log.user_id = user_obj.user_id
154 user_log.user_id = user_obj.user_id
155 user_log.username = user_obj.username
155 user_log.username = user_obj.username
156 user_log.action = safe_unicode(action)
156 user_log.action = safe_unicode(action)
157
157
158 user_log.repository = repo_obj
158 user_log.repository = repo_obj
159 user_log.repository_name = repo_name
159 user_log.repository_name = repo_name
160
160
161 user_log.action_date = datetime.datetime.now()
161 user_log.action_date = datetime.datetime.now()
162 user_log.user_ip = ipaddr
162 user_log.user_ip = ipaddr
163 sa.add(user_log)
163 sa.add(user_log)
164
164
165 log.info('Logging action:%s on %s by user:%s ip:%s' %
165 log.info('Logging action:%s on %s by user:%s ip:%s' %
166 (action, safe_unicode(repo), user_obj, ipaddr))
166 (action, safe_unicode(repo), user_obj, ipaddr))
167 if commit:
167 if commit:
168 sa.commit()
168 sa.commit()
169 except Exception:
169 except Exception:
170 log.error(traceback.format_exc())
170 log.error(traceback.format_exc())
171 raise
171 raise
172
172
173
173
174 def get_filesystem_repos(path, recursive=False, skip_removed_repos=True):
174 def get_filesystem_repos(path, recursive=False, skip_removed_repos=True):
175 """
175 """
176 Scans given path for repos and return (name,(type,path)) tuple
176 Scans given path for repos and return (name,(type,path)) tuple
177
177
178 :param path: path to scan for repositories
178 :param path: path to scan for repositories
179 :param recursive: recursive search and return names with subdirs in front
179 :param recursive: recursive search and return names with subdirs in front
180 """
180 """
181
181
182 # remove ending slash for better results
182 # remove ending slash for better results
183 path = path.rstrip(os.sep)
183 path = path.rstrip(os.sep)
184 log.debug('now scanning in %s location recursive:%s...' % (path, recursive))
184 log.debug('now scanning in %s location recursive:%s...' % (path, recursive))
185
185
186 def _get_repos(p):
186 def _get_repos(p):
187 if not os.access(p, os.W_OK):
187 if not os.access(p, os.W_OK):
188 log.warn('ignoring repo path without write access: %s', p)
188 log.warn('ignoring repo path without write access: %s', p)
189 return
189 return
190 for dirpath in os.listdir(p):
190 for dirpath in os.listdir(p):
191 if os.path.isfile(os.path.join(p, dirpath)):
191 if os.path.isfile(os.path.join(p, dirpath)):
192 continue
192 continue
193 cur_path = os.path.join(p, dirpath)
193 cur_path = os.path.join(p, dirpath)
194
194
195 # skip removed repos
195 # skip removed repos
196 if skip_removed_repos and REMOVED_REPO_PAT.match(dirpath):
196 if skip_removed_repos and REMOVED_REPO_PAT.match(dirpath):
197 continue
197 continue
198
198
199 #skip .<somethin> dirs
199 #skip .<somethin> dirs
200 if dirpath.startswith('.'):
200 if dirpath.startswith('.'):
201 continue
201 continue
202
202
203 try:
203 try:
204 scm_info = get_scm(cur_path)
204 scm_info = get_scm(cur_path)
205 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
205 yield scm_info[1].split(path, 1)[-1].lstrip(os.sep), scm_info
206 except VCSError:
206 except VCSError:
207 if not recursive:
207 if not recursive:
208 continue
208 continue
209 #check if this dir containts other repos for recursive scan
209 #check if this dir containts other repos for recursive scan
210 rec_path = os.path.join(p, dirpath)
210 rec_path = os.path.join(p, dirpath)
211 if os.path.isdir(rec_path):
211 if os.path.isdir(rec_path):
212 for inner_scm in _get_repos(rec_path):
212 for inner_scm in _get_repos(rec_path):
213 yield inner_scm
213 yield inner_scm
214
214
215 return _get_repos(path)
215 return _get_repos(path)
216
216
217
217
218 def is_valid_repo(repo_name, base_path, scm=None):
218 def is_valid_repo(repo_name, base_path, scm=None):
219 """
219 """
220 Returns True if given path is a valid repository False otherwise.
220 Returns True if given path is a valid repository False otherwise.
221 If scm param is given also compare if given scm is the same as expected
221 If scm param is given also compare if given scm is the same as expected
222 from scm parameter
222 from scm parameter
223
223
224 :param repo_name:
224 :param repo_name:
225 :param base_path:
225 :param base_path:
226 :param scm:
226 :param scm:
227
227
228 :return True: if given path is a valid repository
228 :return True: if given path is a valid repository
229 """
229 """
230 full_path = os.path.join(safe_str(base_path), safe_str(repo_name))
230 full_path = os.path.join(safe_str(base_path), safe_str(repo_name))
231
231
232 try:
232 try:
233 scm_ = get_scm(full_path)
233 scm_ = get_scm(full_path)
234 if scm:
234 if scm:
235 return scm_[0] == scm
235 return scm_[0] == scm
236 return True
236 return True
237 except VCSError:
237 except VCSError:
238 return False
238 return False
239
239
240
240
241 def is_valid_repos_group(repos_group_name, base_path, skip_path_check=False):
241 def is_valid_repos_group(repos_group_name, base_path, skip_path_check=False):
242 """
242 """
243 Returns True if given path is a repository group False otherwise
243 Returns True if given path is a repository group False otherwise
244
244
245 :param repo_name:
245 :param repo_name:
246 :param base_path:
246 :param base_path:
247 """
247 """
248 full_path = os.path.join(safe_str(base_path), safe_str(repos_group_name))
248 full_path = os.path.join(safe_str(base_path), safe_str(repos_group_name))
249
249
250 # check if it's not a repo
250 # check if it's not a repo
251 if is_valid_repo(repos_group_name, base_path):
251 if is_valid_repo(repos_group_name, base_path):
252 return False
252 return False
253
253
254 try:
254 try:
255 # we need to check bare git repos at higher level
255 # we need to check bare git repos at higher level
256 # since we might match branches/hooks/info/objects or possible
256 # since we might match branches/hooks/info/objects or possible
257 # other things inside bare git repo
257 # other things inside bare git repo
258 get_scm(os.path.dirname(full_path))
258 get_scm(os.path.dirname(full_path))
259 return False
259 return False
260 except VCSError:
260 except VCSError:
261 pass
261 pass
262
262
263 # check if it's a valid path
263 # check if it's a valid path
264 if skip_path_check or os.path.isdir(full_path):
264 if skip_path_check or os.path.isdir(full_path):
265 return True
265 return True
266
266
267 return False
267 return False
268
268
269
269
270 def ask_ok(prompt, retries=4, complaint='Yes or no please!'):
270 def ask_ok(prompt, retries=4, complaint='Yes or no please!'):
271 while True:
271 while True:
272 ok = raw_input(prompt)
272 ok = raw_input(prompt)
273 if ok in ('y', 'ye', 'yes'):
273 if ok in ('y', 'ye', 'yes'):
274 return True
274 return True
275 if ok in ('n', 'no', 'nop', 'nope'):
275 if ok in ('n', 'no', 'nop', 'nope'):
276 return False
276 return False
277 retries = retries - 1
277 retries = retries - 1
278 if retries < 0:
278 if retries < 0:
279 raise IOError
279 raise IOError
280 print complaint
280 print complaint
281
281
282 #propagated from mercurial documentation
282 #propagated from mercurial documentation
283 ui_sections = ['alias', 'auth',
283 ui_sections = ['alias', 'auth',
284 'decode/encode', 'defaults',
284 'decode/encode', 'defaults',
285 'diff', 'email',
285 'diff', 'email',
286 'extensions', 'format',
286 'extensions', 'format',
287 'merge-patterns', 'merge-tools',
287 'merge-patterns', 'merge-tools',
288 'hooks', 'http_proxy',
288 'hooks', 'http_proxy',
289 'smtp', 'patch',
289 'smtp', 'patch',
290 'paths', 'profiling',
290 'paths', 'profiling',
291 'server', 'trusted',
291 'server', 'trusted',
292 'ui', 'web', ]
292 'ui', 'web', ]
293
293
294
294
295 def make_ui(read_from='file', path=None, checkpaths=True, clear_session=True):
295 def make_ui(read_from='file', path=None, checkpaths=True, clear_session=True):
296 """
296 """
297 A function that will read python rc files or database
297 A function that will read python rc files or database
298 and make an mercurial ui object from read options
298 and make an mercurial ui object from read options
299
299
300 :param path: path to mercurial config file
300 :param path: path to mercurial config file
301 :param checkpaths: check the path
301 :param checkpaths: check the path
302 :param read_from: read from 'file' or 'db'
302 :param read_from: read from 'file' or 'db'
303 """
303 """
304
304
305 baseui = ui.ui()
305 baseui = ui.ui()
306
306
307 # clean the baseui object
307 # clean the baseui object
308 baseui._ocfg = config.config()
308 baseui._ocfg = config.config()
309 baseui._ucfg = config.config()
309 baseui._ucfg = config.config()
310 baseui._tcfg = config.config()
310 baseui._tcfg = config.config()
311
311
312 if read_from == 'file':
312 if read_from == 'file':
313 if not os.path.isfile(path):
313 if not os.path.isfile(path):
314 log.debug('hgrc file is not present at %s, skipping...' % path)
314 log.debug('hgrc file is not present at %s, skipping...' % path)
315 return False
315 return False
316 log.debug('reading hgrc from %s' % path)
316 log.debug('reading hgrc from %s' % path)
317 cfg = config.config()
317 cfg = config.config()
318 cfg.read(path)
318 cfg.read(path)
319 for section in ui_sections:
319 for section in ui_sections:
320 for k, v in cfg.items(section):
320 for k, v in cfg.items(section):
321 log.debug('settings ui from file: [%s] %s=%s' % (section, k, v))
321 log.debug('settings ui from file: [%s] %s=%s' % (section, k, v))
322 baseui.setconfig(safe_str(section), safe_str(k), safe_str(v))
322 baseui.setconfig(safe_str(section), safe_str(k), safe_str(v))
323
323
324 elif read_from == 'db':
324 elif read_from == 'db':
325 sa = meta.Session()
325 sa = meta.Session()
326 ret = sa.query(RhodeCodeUi)\
326 ret = sa.query(RhodeCodeUi)\
327 .options(FromCache("sql_cache_short", "get_hg_ui_settings"))\
327 .options(FromCache("sql_cache_short", "get_hg_ui_settings"))\
328 .all()
328 .all()
329
329
330 hg_ui = ret
330 hg_ui = ret
331 for ui_ in hg_ui:
331 for ui_ in hg_ui:
332 if ui_.ui_active:
332 if ui_.ui_active:
333 log.debug('settings ui from db: [%s] %s=%s', ui_.ui_section,
333 log.debug('settings ui from db: [%s] %s=%s', ui_.ui_section,
334 ui_.ui_key, ui_.ui_value)
334 ui_.ui_key, ui_.ui_value)
335 baseui.setconfig(safe_str(ui_.ui_section), safe_str(ui_.ui_key),
335 baseui.setconfig(safe_str(ui_.ui_section), safe_str(ui_.ui_key),
336 safe_str(ui_.ui_value))
336 safe_str(ui_.ui_value))
337 if ui_.ui_key == 'push_ssl':
337 if ui_.ui_key == 'push_ssl':
338 # force set push_ssl requirement to False, rhodecode
338 # force set push_ssl requirement to False, rhodecode
339 # handles that
339 # handles that
340 baseui.setconfig(safe_str(ui_.ui_section), safe_str(ui_.ui_key),
340 baseui.setconfig(safe_str(ui_.ui_section), safe_str(ui_.ui_key),
341 False)
341 False)
342 if clear_session:
342 if clear_session:
343 meta.Session.remove()
343 meta.Session.remove()
344 return baseui
344 return baseui
345
345
346
346
347 def set_rhodecode_config(config):
347 def set_rhodecode_config(config):
348 """
348 """
349 Updates pylons config with new settings from database
349 Updates pylons config with new settings from database
350
350
351 :param config:
351 :param config:
352 """
352 """
353 hgsettings = RhodeCodeSetting.get_app_settings()
353 hgsettings = RhodeCodeSetting.get_app_settings()
354
354
355 for k, v in hgsettings.items():
355 for k, v in hgsettings.items():
356 config[k] = v
356 config[k] = v
357
357
358
358
359 def map_groups(path):
359 def map_groups(path):
360 """
360 """
361 Given a full path to a repository, create all nested groups that this
361 Given a full path to a repository, create all nested groups that this
362 repo is inside. This function creates parent-child relationships between
362 repo is inside. This function creates parent-child relationships between
363 groups and creates default perms for all new groups.
363 groups and creates default perms for all new groups.
364
364
365 :param paths: full path to repository
365 :param paths: full path to repository
366 """
366 """
367 sa = meta.Session()
367 sa = meta.Session()
368 groups = path.split(Repository.url_sep())
368 groups = path.split(Repository.url_sep())
369 parent = None
369 parent = None
370 group = None
370 group = None
371
371
372 # last element is repo in nested groups structure
372 # last element is repo in nested groups structure
373 groups = groups[:-1]
373 groups = groups[:-1]
374 rgm = ReposGroupModel(sa)
374 rgm = ReposGroupModel(sa)
375 for lvl, group_name in enumerate(groups):
375 for lvl, group_name in enumerate(groups):
376 group_name = '/'.join(groups[:lvl] + [group_name])
376 group_name = '/'.join(groups[:lvl] + [group_name])
377 group = RepoGroup.get_by_group_name(group_name)
377 group = RepoGroup.get_by_group_name(group_name)
378 desc = '%s group' % group_name
378 desc = '%s group' % group_name
379
379
380 # skip folders that are now removed repos
380 # skip folders that are now removed repos
381 if REMOVED_REPO_PAT.match(group_name):
381 if REMOVED_REPO_PAT.match(group_name):
382 break
382 break
383
383
384 if group is None:
384 if group is None:
385 log.debug('creating group level: %s group_name: %s' % (lvl,
385 log.debug('creating group level: %s group_name: %s' % (lvl,
386 group_name))
386 group_name))
387 group = RepoGroup(group_name, parent)
387 group = RepoGroup(group_name, parent)
388 group.group_description = desc
388 group.group_description = desc
389 sa.add(group)
389 sa.add(group)
390 rgm._create_default_perms(group)
390 rgm._create_default_perms(group)
391 sa.flush()
391 sa.flush()
392 parent = group
392 parent = group
393 return group
393 return group
394
394
395
395
396 def repo2db_mapper(initial_repo_list, remove_obsolete=False,
396 def repo2db_mapper(initial_repo_list, remove_obsolete=False,
397 install_git_hook=False):
397 install_git_hook=False):
398 """
398 """
399 maps all repos given in initial_repo_list, non existing repositories
399 maps all repos given in initial_repo_list, non existing repositories
400 are created, if remove_obsolete is True it also check for db entries
400 are created, if remove_obsolete is True it also check for db entries
401 that are not in initial_repo_list and removes them.
401 that are not in initial_repo_list and removes them.
402
402
403 :param initial_repo_list: list of repositories found by scanning methods
403 :param initial_repo_list: list of repositories found by scanning methods
404 :param remove_obsolete: check for obsolete entries in database
404 :param remove_obsolete: check for obsolete entries in database
405 :param install_git_hook: if this is True, also check and install githook
405 :param install_git_hook: if this is True, also check and install githook
406 for a repo if missing
406 for a repo if missing
407 """
407 """
408 from rhodecode.model.repo import RepoModel
408 from rhodecode.model.repo import RepoModel
409 from rhodecode.model.scm import ScmModel
409 from rhodecode.model.scm import ScmModel
410 sa = meta.Session()
410 sa = meta.Session()
411 rm = RepoModel()
411 rm = RepoModel()
412 user = sa.query(User).filter(User.admin == True).first()
412 user = sa.query(User).filter(User.admin == True).first()
413 if user is None:
413 if user is None:
414 raise Exception('Missing administrative account!')
414 raise Exception('Missing administrative account!')
415 added = []
415 added = []
416
416
417 ##creation defaults
417 ##creation defaults
418 defs = RhodeCodeSetting.get_default_repo_settings(strip_prefix=True)
418 defs = RhodeCodeSetting.get_default_repo_settings(strip_prefix=True)
419 enable_statistics = defs.get('repo_enable_statistics')
419 enable_statistics = defs.get('repo_enable_statistics')
420 enable_locking = defs.get('repo_enable_locking')
420 enable_locking = defs.get('repo_enable_locking')
421 enable_downloads = defs.get('repo_enable_downloads')
421 enable_downloads = defs.get('repo_enable_downloads')
422 private = defs.get('repo_private')
422 private = defs.get('repo_private')
423
423
424 for name, repo in initial_repo_list.items():
424 for name, repo in initial_repo_list.items():
425 group = map_groups(name)
425 group = map_groups(name)
426 db_repo = rm.get_by_repo_name(name)
426 db_repo = rm.get_by_repo_name(name)
427 # found repo that is on filesystem not in RhodeCode database
427 # found repo that is on filesystem not in RhodeCode database
428 if not db_repo:
428 if not db_repo:
429 log.info('repository %s not found, creating now' % name)
429 log.info('repository %s not found, creating now' % name)
430 added.append(name)
430 added.append(name)
431 desc = (repo.description
431 desc = (repo.description
432 if repo.description != 'unknown'
432 if repo.description != 'unknown'
433 else '%s repository' % name)
433 else '%s repository' % name)
434
434
435 new_repo = rm.create_repo(
435 new_repo = rm.create_repo(
436 repo_name=name,
436 repo_name=name,
437 repo_type=repo.alias,
437 repo_type=repo.alias,
438 description=desc,
438 description=desc,
439 repos_group=getattr(group, 'group_id', None),
439 repos_group=getattr(group, 'group_id', None),
440 owner=user,
440 owner=user,
441 just_db=True,
441 just_db=True,
442 enable_locking=enable_locking,
442 enable_locking=enable_locking,
443 enable_downloads=enable_downloads,
443 enable_downloads=enable_downloads,
444 enable_statistics=enable_statistics,
444 enable_statistics=enable_statistics,
445 private=private
445 private=private
446 )
446 )
447 # we added that repo just now, and make sure it has githook
447 # we added that repo just now, and make sure it has githook
448 # installed
448 # installed
449 if new_repo.repo_type == 'git':
449 if new_repo.repo_type == 'git':
450 ScmModel().install_git_hook(new_repo.scm_instance)
450 ScmModel().install_git_hook(new_repo.scm_instance)
451 new_repo.update_changeset_cache()
451 new_repo.update_changeset_cache()
452 elif install_git_hook:
452 elif install_git_hook:
453 if db_repo.repo_type == 'git':
453 if db_repo.repo_type == 'git':
454 ScmModel().install_git_hook(db_repo.scm_instance)
454 ScmModel().install_git_hook(db_repo.scm_instance)
455 # during starting install all cache keys for all repositories in the
455 # during starting install all cache keys for all repositories in the
456 # system, this will register all repos and multiple instances
456 # system, this will register all repos and multiple instances
457 cache_key = CacheInvalidation._get_cache_key(name)
457 cache_key = CacheInvalidation._get_cache_key(name)
458 log.debug("Creating invalidation cache key for %s: %s", name, cache_key)
458 log.debug("Creating invalidation cache key for %s: %s", name, cache_key)
459 CacheInvalidation.invalidate(name)
459 CacheInvalidation.invalidate(name)
460
460
461 sa.commit()
461 sa.commit()
462 removed = []
462 removed = []
463 if remove_obsolete:
463 if remove_obsolete:
464 # remove from database those repositories that are not in the filesystem
464 # remove from database those repositories that are not in the filesystem
465 for repo in sa.query(Repository).all():
465 for repo in sa.query(Repository).all():
466 if repo.repo_name not in initial_repo_list.keys():
466 if repo.repo_name not in initial_repo_list.keys():
467 log.debug("Removing non-existing repository found in db `%s`" %
467 log.debug("Removing non-existing repository found in db `%s`" %
468 repo.repo_name)
468 repo.repo_name)
469 try:
469 try:
470 removed.append(repo.repo_name)
470 removed.append(repo.repo_name)
471 RepoModel(sa).delete(repo, forks='detach', fs_remove=False)
471 RepoModel(sa).delete(repo, forks='detach', fs_remove=False)
472 sa.commit()
472 sa.commit()
473 except Exception:
473 except Exception:
474 #don't hold further removals on error
474 #don't hold further removals on error
475 log.error(traceback.format_exc())
475 log.error(traceback.format_exc())
476 sa.rollback()
476 sa.rollback()
477 return added, removed
477 return added, removed
478
478
479
479
480 # set cache regions for beaker so celery can utilise it
480 # set cache regions for beaker so celery can utilise it
481 def add_cache(settings):
481 def add_cache(settings):
482 cache_settings = {'regions': None}
482 cache_settings = {'regions': None}
483 for key in settings.keys():
483 for key in settings.keys():
484 for prefix in ['beaker.cache.', 'cache.']:
484 for prefix in ['beaker.cache.', 'cache.']:
485 if key.startswith(prefix):
485 if key.startswith(prefix):
486 name = key.split(prefix)[1].strip()
486 name = key.split(prefix)[1].strip()
487 cache_settings[name] = settings[key].strip()
487 cache_settings[name] = settings[key].strip()
488 if cache_settings['regions']:
488 if cache_settings['regions']:
489 for region in cache_settings['regions'].split(','):
489 for region in cache_settings['regions'].split(','):
490 region = region.strip()
490 region = region.strip()
491 region_settings = {}
491 region_settings = {}
492 for key, value in cache_settings.items():
492 for key, value in cache_settings.items():
493 if key.startswith(region):
493 if key.startswith(region):
494 region_settings[key.split('.')[1]] = value
494 region_settings[key.split('.')[1]] = value
495 region_settings['expire'] = int(region_settings.get('expire',
495 region_settings['expire'] = int(region_settings.get('expire',
496 60))
496 60))
497 region_settings.setdefault('lock_dir',
497 region_settings.setdefault('lock_dir',
498 cache_settings.get('lock_dir'))
498 cache_settings.get('lock_dir'))
499 region_settings.setdefault('data_dir',
499 region_settings.setdefault('data_dir',
500 cache_settings.get('data_dir'))
500 cache_settings.get('data_dir'))
501
501
502 if 'type' not in region_settings:
502 if 'type' not in region_settings:
503 region_settings['type'] = cache_settings.get('type',
503 region_settings['type'] = cache_settings.get('type',
504 'memory')
504 'memory')
505 beaker.cache.cache_regions[region] = region_settings
505 beaker.cache.cache_regions[region] = region_settings
506
506
507
507
508 def load_rcextensions(root_path):
508 def load_rcextensions(root_path):
509 import rhodecode
509 import rhodecode
510 from rhodecode.config import conf
510 from rhodecode.config import conf
511
511
512 path = os.path.join(root_path, 'rcextensions', '__init__.py')
512 path = os.path.join(root_path, 'rcextensions', '__init__.py')
513 if os.path.isfile(path):
513 if os.path.isfile(path):
514 rcext = create_module('rc', path)
514 rcext = create_module('rc', path)
515 EXT = rhodecode.EXTENSIONS = rcext
515 EXT = rhodecode.EXTENSIONS = rcext
516 log.debug('Found rcextensions now loading %s...' % rcext)
516 log.debug('Found rcextensions now loading %s...' % rcext)
517
517
518 # Additional mappings that are not present in the pygments lexers
518 # Additional mappings that are not present in the pygments lexers
519 conf.LANGUAGES_EXTENSIONS_MAP.update(getattr(EXT, 'EXTRA_MAPPINGS', {}))
519 conf.LANGUAGES_EXTENSIONS_MAP.update(getattr(EXT, 'EXTRA_MAPPINGS', {}))
520
520
521 #OVERRIDE OUR EXTENSIONS FROM RC-EXTENSIONS (if present)
521 #OVERRIDE OUR EXTENSIONS FROM RC-EXTENSIONS (if present)
522
522
523 if getattr(EXT, 'INDEX_EXTENSIONS', []) != []:
523 if getattr(EXT, 'INDEX_EXTENSIONS', []) != []:
524 log.debug('settings custom INDEX_EXTENSIONS')
524 log.debug('settings custom INDEX_EXTENSIONS')
525 conf.INDEX_EXTENSIONS = getattr(EXT, 'INDEX_EXTENSIONS', [])
525 conf.INDEX_EXTENSIONS = getattr(EXT, 'INDEX_EXTENSIONS', [])
526
526
527 #ADDITIONAL MAPPINGS
527 #ADDITIONAL MAPPINGS
528 log.debug('adding extra into INDEX_EXTENSIONS')
528 log.debug('adding extra into INDEX_EXTENSIONS')
529 conf.INDEX_EXTENSIONS.extend(getattr(EXT, 'EXTRA_INDEX_EXTENSIONS', []))
529 conf.INDEX_EXTENSIONS.extend(getattr(EXT, 'EXTRA_INDEX_EXTENSIONS', []))
530
530
531 # auto check if the module is not missing any data, set to default if is
531 # auto check if the module is not missing any data, set to default if is
532 # this will help autoupdate new feature of rcext module
532 # this will help autoupdate new feature of rcext module
533 from rhodecode.config import rcextensions
533 from rhodecode.config import rcextensions
534 for k in dir(rcextensions):
534 for k in dir(rcextensions):
535 if not k.startswith('_') and not hasattr(EXT, k):
535 if not k.startswith('_') and not hasattr(EXT, k):
536 setattr(EXT, k, getattr(rcextensions, k))
536 setattr(EXT, k, getattr(rcextensions, k))
537
537
538
538
539 def get_custom_lexer(extension):
539 def get_custom_lexer(extension):
540 """
540 """
541 returns a custom lexer if it's defined in rcextensions module, or None
541 returns a custom lexer if it's defined in rcextensions module, or None
542 if there's no custom lexer defined
542 if there's no custom lexer defined
543 """
543 """
544 import rhodecode
544 import rhodecode
545 from pygments import lexers
545 from pygments import lexers
546 #check if we didn't define this extension as other lexer
546 #check if we didn't define this extension as other lexer
547 if rhodecode.EXTENSIONS and extension in rhodecode.EXTENSIONS.EXTRA_LEXERS:
547 if rhodecode.EXTENSIONS and extension in rhodecode.EXTENSIONS.EXTRA_LEXERS:
548 _lexer_name = rhodecode.EXTENSIONS.EXTRA_LEXERS[extension]
548 _lexer_name = rhodecode.EXTENSIONS.EXTRA_LEXERS[extension]
549 return lexers.get_lexer_by_name(_lexer_name)
549 return lexers.get_lexer_by_name(_lexer_name)
550
550
551
551
552 #==============================================================================
552 #==============================================================================
553 # TEST FUNCTIONS AND CREATORS
553 # TEST FUNCTIONS AND CREATORS
554 #==============================================================================
554 #==============================================================================
555 def create_test_index(repo_location, config, full_index):
555 def create_test_index(repo_location, config, full_index):
556 """
556 """
557 Makes default test index
557 Makes default test index
558
558
559 :param config: test config
559 :param config: test config
560 :param full_index:
560 :param full_index:
561 """
561 """
562
562
563 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
563 from rhodecode.lib.indexers.daemon import WhooshIndexingDaemon
564 from rhodecode.lib.pidlock import DaemonLock, LockHeld
564 from rhodecode.lib.pidlock import DaemonLock, LockHeld
565
565
566 repo_location = repo_location
566 repo_location = repo_location
567
567
568 index_location = os.path.join(config['app_conf']['index_dir'])
568 index_location = os.path.join(config['app_conf']['index_dir'])
569 if not os.path.exists(index_location):
569 if not os.path.exists(index_location):
570 os.makedirs(index_location)
570 os.makedirs(index_location)
571
571
572 try:
572 try:
573 l = DaemonLock(file_=jn(dn(index_location), 'make_index.lock'))
573 l = DaemonLock(file_=jn(dn(index_location), 'make_index.lock'))
574 WhooshIndexingDaemon(index_location=index_location,
574 WhooshIndexingDaemon(index_location=index_location,
575 repo_location=repo_location)\
575 repo_location=repo_location)\
576 .run(full_index=full_index)
576 .run(full_index=full_index)
577 l.release()
577 l.release()
578 except LockHeld:
578 except LockHeld:
579 pass
579 pass
580
580
581
581
582 def create_test_env(repos_test_path, config):
582 def create_test_env(repos_test_path, config):
583 """
583 """
584 Makes a fresh database and
584 Makes a fresh database and
585 install test repository into tmp dir
585 install test repository into tmp dir
586 """
586 """
587 from rhodecode.lib.db_manage import DbManage
587 from rhodecode.lib.db_manage import DbManage
588 from rhodecode.tests import HG_REPO, GIT_REPO, TESTS_TMP_PATH
588 from rhodecode.tests import HG_REPO, GIT_REPO, TESTS_TMP_PATH
589
589
590 # PART ONE create db
590 # PART ONE create db
591 dbconf = config['sqlalchemy.db1.url']
591 dbconf = config['sqlalchemy.db1.url']
592 log.debug('making test db %s' % dbconf)
592 log.debug('making test db %s' % dbconf)
593
593
594 # create test dir if it doesn't exist
594 # create test dir if it doesn't exist
595 if not os.path.isdir(repos_test_path):
595 if not os.path.isdir(repos_test_path):
596 log.debug('Creating testdir %s' % repos_test_path)
596 log.debug('Creating testdir %s' % repos_test_path)
597 os.makedirs(repos_test_path)
597 os.makedirs(repos_test_path)
598
598
599 dbmanage = DbManage(log_sql=True, dbconf=dbconf, root=config['here'],
599 dbmanage = DbManage(log_sql=True, dbconf=dbconf, root=config['here'],
600 tests=True)
600 tests=True)
601 dbmanage.create_tables(override=True)
601 dbmanage.create_tables(override=True)
602 dbmanage.create_settings(dbmanage.config_prompt(repos_test_path))
602 dbmanage.create_settings(dbmanage.config_prompt(repos_test_path))
603 dbmanage.create_default_user()
603 dbmanage.create_default_user()
604 dbmanage.admin_prompt()
604 dbmanage.admin_prompt()
605 dbmanage.create_permissions()
605 dbmanage.create_permissions()
606 dbmanage.populate_default_permissions()
606 dbmanage.populate_default_permissions()
607 Session().commit()
607 Session().commit()
608 # PART TWO make test repo
608 # PART TWO make test repo
609 log.debug('making test vcs repositories')
609 log.debug('making test vcs repositories')
610
610
611 idx_path = config['app_conf']['index_dir']
611 idx_path = config['app_conf']['index_dir']
612 data_path = config['app_conf']['cache_dir']
612 data_path = config['app_conf']['cache_dir']
613
613
614 #clean index and data
614 #clean index and data
615 if idx_path and os.path.exists(idx_path):
615 if idx_path and os.path.exists(idx_path):
616 log.debug('remove %s' % idx_path)
616 log.debug('remove %s' % idx_path)
617 shutil.rmtree(idx_path)
617 shutil.rmtree(idx_path)
618
618
619 if data_path and os.path.exists(data_path):
619 if data_path and os.path.exists(data_path):
620 log.debug('remove %s' % data_path)
620 log.debug('remove %s' % data_path)
621 shutil.rmtree(data_path)
621 shutil.rmtree(data_path)
622
622
623 #CREATE DEFAULT TEST REPOS
623 #CREATE DEFAULT TEST REPOS
624 cur_dir = dn(dn(abspath(__file__)))
624 cur_dir = dn(dn(abspath(__file__)))
625 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_hg.tar.gz"))
625 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_hg.tar.gz"))
626 tar.extractall(jn(TESTS_TMP_PATH, HG_REPO))
626 tar.extractall(jn(TESTS_TMP_PATH, HG_REPO))
627 tar.close()
627 tar.close()
628
628
629 cur_dir = dn(dn(abspath(__file__)))
629 cur_dir = dn(dn(abspath(__file__)))
630 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_git.tar.gz"))
630 tar = tarfile.open(jn(cur_dir, 'tests', "vcs_test_git.tar.gz"))
631 tar.extractall(jn(TESTS_TMP_PATH, GIT_REPO))
631 tar.extractall(jn(TESTS_TMP_PATH, GIT_REPO))
632 tar.close()
632 tar.close()
633
633
634 #LOAD VCS test stuff
634 #LOAD VCS test stuff
635 from rhodecode.tests.vcs import setup_package
635 from rhodecode.tests.vcs import setup_package
636 setup_package()
636 setup_package()
637
637
638
638
639 #==============================================================================
639 #==============================================================================
640 # PASTER COMMANDS
640 # PASTER COMMANDS
641 #==============================================================================
641 #==============================================================================
642 class BasePasterCommand(Command):
642 class BasePasterCommand(Command):
643 """
643 """
644 Abstract Base Class for paster commands.
644 Abstract Base Class for paster commands.
645
645
646 The celery commands are somewhat aggressive about loading
646 The celery commands are somewhat aggressive about loading
647 celery.conf, and since our module sets the `CELERY_LOADER`
647 celery.conf, and since our module sets the `CELERY_LOADER`
648 environment variable to our loader, we have to bootstrap a bit and
648 environment variable to our loader, we have to bootstrap a bit and
649 make sure we've had a chance to load the pylons config off of the
649 make sure we've had a chance to load the pylons config off of the
650 command line, otherwise everything fails.
650 command line, otherwise everything fails.
651 """
651 """
652 min_args = 1
652 min_args = 1
653 min_args_error = "Please provide a paster config file as an argument."
653 min_args_error = "Please provide a paster config file as an argument."
654 takes_config_file = 1
654 takes_config_file = 1
655 requires_config_file = True
655 requires_config_file = True
656
656
657 def notify_msg(self, msg, log=False):
657 def notify_msg(self, msg, log=False):
658 """Make a notification to user, additionally if logger is passed
658 """Make a notification to user, additionally if logger is passed
659 it logs this action using given logger
659 it logs this action using given logger
660
660
661 :param msg: message that will be printed to user
661 :param msg: message that will be printed to user
662 :param log: logging instance, to use to additionally log this message
662 :param log: logging instance, to use to additionally log this message
663
663
664 """
664 """
665 if log and isinstance(log, logging):
665 if log and isinstance(log, logging):
666 log(msg)
666 log(msg)
667
667
668 def run(self, args):
668 def run(self, args):
669 """
669 """
670 Overrides Command.run
670 Overrides Command.run
671
671
672 Checks for a config file argument and loads it.
672 Checks for a config file argument and loads it.
673 """
673 """
674 if len(args) < self.min_args:
674 if len(args) < self.min_args:
675 raise BadCommand(
675 raise BadCommand(
676 self.min_args_error % {'min_args': self.min_args,
676 self.min_args_error % {'min_args': self.min_args,
677 'actual_args': len(args)})
677 'actual_args': len(args)})
678
678
679 # Decrement because we're going to lob off the first argument.
679 # Decrement because we're going to lob off the first argument.
680 # @@ This is hacky
680 # @@ This is hacky
681 self.min_args -= 1
681 self.min_args -= 1
682 self.bootstrap_config(args[0])
682 self.bootstrap_config(args[0])
683 self.update_parser()
683 self.update_parser()
684 return super(BasePasterCommand, self).run(args[1:])
684 return super(BasePasterCommand, self).run(args[1:])
685
685
686 def update_parser(self):
686 def update_parser(self):
687 """
687 """
688 Abstract method. Allows for the class's parser to be updated
688 Abstract method. Allows for the class's parser to be updated
689 before the superclass's `run` method is called. Necessary to
689 before the superclass's `run` method is called. Necessary to
690 allow options/arguments to be passed through to the underlying
690 allow options/arguments to be passed through to the underlying
691 celery command.
691 celery command.
692 """
692 """
693 raise NotImplementedError("Abstract Method.")
693 raise NotImplementedError("Abstract Method.")
694
694
695 def bootstrap_config(self, conf):
695 def bootstrap_config(self, conf):
696 """
696 """
697 Loads the pylons configuration.
697 Loads the pylons configuration.
698 """
698 """
699 from pylons import config as pylonsconfig
699 from pylons import config as pylonsconfig
700
700
701 self.path_to_ini_file = os.path.realpath(conf)
701 self.path_to_ini_file = os.path.realpath(conf)
702 conf = paste.deploy.appconfig('config:' + self.path_to_ini_file)
702 conf = paste.deploy.appconfig('config:' + self.path_to_ini_file)
703 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
703 pylonsconfig.init_app(conf.global_conf, conf.local_conf)
704
704
705 def _init_session(self):
705 def _init_session(self):
706 """
706 """
707 Inits SqlAlchemy Session
707 Inits SqlAlchemy Session
708 """
708 """
709 logging.config.fileConfig(self.path_to_ini_file)
709 logging.config.fileConfig(self.path_to_ini_file)
710 from pylons import config
710 from pylons import config
711 from rhodecode.model import init_model
711 from rhodecode.model import init_model
712 from rhodecode.lib.utils2 import engine_from_config
712 from rhodecode.lib.utils2 import engine_from_config
713
713
714 #get to remove repos !!
714 #get to remove repos !!
715 add_cache(config)
715 add_cache(config)
716 engine = engine_from_config(config, 'sqlalchemy.db1.')
716 engine = engine_from_config(config, 'sqlalchemy.db1.')
717 init_model(engine)
717 init_model(engine)
718
718
719
719
720 def check_git_version():
720 def check_git_version():
721 """
721 """
722 Checks what version of git is installed in system, and issues a warning
722 Checks what version of git is installed in system, and issues a warning
723 if it's too old for RhodeCode to properly work.
723 if it's too old for RhodeCode to properly work.
724 """
724 """
725 from rhodecode import BACKENDS
725 from rhodecode import BACKENDS
726 from rhodecode.lib.vcs.backends.git.repository import GitRepository
726 from rhodecode.lib.vcs.backends.git.repository import GitRepository
727 from distutils.version import StrictVersion
727 from distutils.version import StrictVersion
728
728
729 stdout, stderr = GitRepository._run_git_command('--version', _bare=True,
729 stdout, stderr = GitRepository._run_git_command('--version', _bare=True,
730 _safe=True)
730 _safe=True)
731
731
732 ver = (stdout.split(' ')[-1] or '').strip() or '0.0.0'
732 ver = (stdout.split(' ')[-1] or '').strip() or '0.0.0'
733 if len(ver.split('.')) > 3:
733 if len(ver.split('.')) > 3:
734 #StrictVersion needs to be only 3 element type
734 #StrictVersion needs to be only 3 element type
735 ver = '.'.join(ver.split('.')[:3])
735 ver = '.'.join(ver.split('.')[:3])
736 try:
736 try:
737 _ver = StrictVersion(ver)
737 _ver = StrictVersion(ver)
738 except Exception:
738 except Exception:
739 _ver = StrictVersion('0.0.0')
739 _ver = StrictVersion('0.0.0')
740 stderr = traceback.format_exc()
740 stderr = traceback.format_exc()
741
741
742 req_ver = '1.7.4'
742 req_ver = '1.7.4'
743 to_old_git = False
743 to_old_git = False
744 if _ver < StrictVersion(req_ver):
744 if _ver < StrictVersion(req_ver):
745 to_old_git = True
745 to_old_git = True
746
746
747 if 'git' in BACKENDS:
747 if 'git' in BACKENDS:
748 log.debug('GIT version detected: %s' % stdout)
748 log.debug('GIT version detected: %s' % stdout)
749 if stderr:
749 if stderr:
750 log.warning('Unable to detect git version, org error was: %r' % stderr)
750 log.warning('Unable to detect git version, org error was: %r' % stderr)
751 elif to_old_git:
751 elif to_old_git:
752 log.warning('RhodeCode detected git version %s, which is too old '
752 log.warning('RhodeCode detected git version %s, which is too old '
753 'for the system to function properly. Make sure '
753 'for the system to function properly. Make sure '
754 'its version is at least %s' % (ver, req_ver))
754 'its version is at least %s' % (ver, req_ver))
755 return _ver
755 return _ver
756
756
757
757
758 @decorator.decorator
758 @decorator.decorator
759 def jsonify(func, *args, **kwargs):
759 def jsonify(func, *args, **kwargs):
760 """Action decorator that formats output for JSON
760 """Action decorator that formats output for JSON
761
761
762 Given a function that will return content, this decorator will turn
762 Given a function that will return content, this decorator will turn
763 the result into JSON, with a content-type of 'application/json' and
763 the result into JSON, with a content-type of 'application/json' and
764 output it.
764 output it.
765
765
766 """
766 """
767 from pylons.decorators.util import get_pylons
767 from pylons.decorators.util import get_pylons
768 from rhodecode.lib.ext_json import json
768 from rhodecode.lib.compat import json
769 pylons = get_pylons(args)
769 pylons = get_pylons(args)
770 pylons.response.headers['Content-Type'] = 'application/json; charset=utf-8'
770 pylons.response.headers['Content-Type'] = 'application/json; charset=utf-8'
771 data = func(*args, **kwargs)
771 data = func(*args, **kwargs)
772 if isinstance(data, (list, tuple)):
772 if isinstance(data, (list, tuple)):
773 msg = "JSON responses with Array envelopes are susceptible to " \
773 msg = "JSON responses with Array envelopes are susceptible to " \
774 "cross-site data leak attacks, see " \
774 "cross-site data leak attacks, see " \
775 "http://wiki.pylonshq.com/display/pylonsfaq/Warnings"
775 "http://wiki.pylonshq.com/display/pylonsfaq/Warnings"
776 warnings.warn(msg, Warning, 2)
776 warnings.warn(msg, Warning, 2)
777 log.warning(msg)
777 log.warning(msg)
778 log.debug("Returning JSON wrapped action output")
778 log.debug("Returning JSON wrapped action output")
779 return json.dumps(data, encoding='utf-8')
779 return json.dumps(data, encoding='utf-8')
General Comments 0
You need to be logged in to leave comments. Login now