##// END OF EJS Templates
code-review initial
marcink -
r2215:2c2bdaec codereview
parent child Browse files
Show More
@@ -1,135 +1,135 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.changelog
3 rhodecode.controllers.changelog
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 changelog controller for rhodecode
6 changelog controller for rhodecode
7
7
8 :created_on: Apr 21, 2010
8 :created_on: Apr 21, 2010
9 :author: marcink
9 :author: marcink
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
10 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :license: GPLv3, see COPYING for more details.
11 :license: GPLv3, see COPYING for more details.
12 """
12 """
13 # This program is free software: you can redistribute it and/or modify
13 # This program is free software: you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation, either version 3 of the License, or
15 # the Free Software Foundation, either version 3 of the License, or
16 # (at your option) any later version.
16 # (at your option) any later version.
17 #
17 #
18 # This program is distributed in the hope that it will be useful,
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # GNU General Public License for more details.
21 # GNU General Public License for more details.
22 #
22 #
23 # You should have received a copy of the GNU General Public License
23 # You should have received a copy of the GNU General Public License
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
24 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25
25
26 import logging
26 import logging
27 import traceback
27 import traceback
28
28
29 from mercurial import graphmod
29 from mercurial import graphmod
30 from pylons import request, url, session, tmpl_context as c
30 from pylons import request, url, session, tmpl_context as c
31 from pylons.controllers.util import redirect
31 from pylons.controllers.util import redirect
32 from pylons.i18n.translation import _
32 from pylons.i18n.translation import _
33
33
34 import rhodecode.lib.helpers as h
34 import rhodecode.lib.helpers as h
35 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
35 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
36 from rhodecode.lib.base import BaseRepoController, render
36 from rhodecode.lib.base import BaseRepoController, render
37 from rhodecode.lib.helpers import RepoPage
37 from rhodecode.lib.helpers import RepoPage
38 from rhodecode.lib.compat import json
38 from rhodecode.lib.compat import json
39
39
40 from rhodecode.lib.vcs.exceptions import RepositoryError, ChangesetDoesNotExistError
40 from rhodecode.lib.vcs.exceptions import RepositoryError, ChangesetDoesNotExistError
41 from rhodecode.model.db import Repository
41 from rhodecode.model.db import Repository
42
42
43 log = logging.getLogger(__name__)
43 log = logging.getLogger(__name__)
44
44
45
45
46 class ChangelogController(BaseRepoController):
46 class ChangelogController(BaseRepoController):
47
47
48 @LoginRequired()
48 @LoginRequired()
49 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
49 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
50 'repository.admin')
50 'repository.admin')
51 def __before__(self):
51 def __before__(self):
52 super(ChangelogController, self).__before__()
52 super(ChangelogController, self).__before__()
53 c.affected_files_cut_off = 60
53 c.affected_files_cut_off = 60
54
54
55 def index(self):
55 def index(self):
56 limit = 100
56 limit = 100
57 default = 20
57 default = 20
58 if request.params.get('size'):
58 if request.params.get('size'):
59 try:
59 try:
60 int_size = int(request.params.get('size'))
60 int_size = int(request.params.get('size'))
61 except ValueError:
61 except ValueError:
62 int_size = default
62 int_size = default
63 int_size = int_size if int_size <= limit else limit
63 int_size = int_size if int_size <= limit else limit
64 c.size = int_size
64 c.size = int_size
65 session['changelog_size'] = c.size
65 session['changelog_size'] = c.size
66 session.save()
66 session.save()
67 else:
67 else:
68 c.size = int(session.get('changelog_size', default))
68 c.size = int(session.get('changelog_size', default))
69
69
70 p = int(request.params.get('page', 1))
70 p = int(request.params.get('page', 1))
71 branch_name = request.params.get('branch', None)
71 branch_name = request.params.get('branch', None)
72 try:
72 try:
73 if branch_name:
73 if branch_name:
74 collection = [z for z in
74 collection = [z for z in
75 c.rhodecode_repo.get_changesets(start=0,
75 c.rhodecode_repo.get_changesets(start=0,
76 branch_name=branch_name)]
76 branch_name=branch_name)]
77 c.total_cs = len(collection)
77 c.total_cs = len(collection)
78 else:
78 else:
79 collection = c.rhodecode_repo
79 collection = c.rhodecode_repo
80 c.total_cs = len(c.rhodecode_repo)
80 c.total_cs = len(c.rhodecode_repo)
81
81
82 c.pagination = RepoPage(collection, page=p, item_count=c.total_cs,
82 c.pagination = RepoPage(collection, page=p, item_count=c.total_cs,
83 items_per_page=c.size, branch=branch_name)
83 items_per_page=c.size, branch=branch_name)
84 collection = list(c.pagination)
84 collection = list(c.pagination)
85 page_revisions = [x.raw_id for x in collection]
85 page_revisions = [x.raw_id for x in collection]
86 c.comments = c.rhodecode_db_repo.comments(page_revisions)
86 c.comments = c.rhodecode_db_repo.comments(page_revisions)
87
87 c.statuses = c.rhodecode_db_repo.statuses(page_revisions)
88 except (RepositoryError, ChangesetDoesNotExistError, Exception), e:
88 except (RepositoryError, ChangesetDoesNotExistError, Exception), e:
89 log.error(traceback.format_exc())
89 log.error(traceback.format_exc())
90 h.flash(str(e), category='warning')
90 h.flash(str(e), category='warning')
91 return redirect(url('home'))
91 return redirect(url('home'))
92
92
93 self._graph(c.rhodecode_repo, collection, c.total_cs, c.size, p)
93 self._graph(c.rhodecode_repo, collection, c.total_cs, c.size, p)
94
94
95 c.branch_name = branch_name
95 c.branch_name = branch_name
96 c.branch_filters = [('', _('All Branches'))] + \
96 c.branch_filters = [('', _('All Branches'))] + \
97 [(k, k) for k in c.rhodecode_repo.branches.keys()]
97 [(k, k) for k in c.rhodecode_repo.branches.keys()]
98
98
99 return render('changelog/changelog.html')
99 return render('changelog/changelog.html')
100
100
101 def changelog_details(self, cs):
101 def changelog_details(self, cs):
102 if request.environ.get('HTTP_X_PARTIAL_XHR'):
102 if request.environ.get('HTTP_X_PARTIAL_XHR'):
103 c.cs = c.rhodecode_repo.get_changeset(cs)
103 c.cs = c.rhodecode_repo.get_changeset(cs)
104 return render('changelog/changelog_details.html')
104 return render('changelog/changelog_details.html')
105
105
106 def _graph(self, repo, collection, repo_size, size, p):
106 def _graph(self, repo, collection, repo_size, size, p):
107 """
107 """
108 Generates a DAG graph for mercurial
108 Generates a DAG graph for mercurial
109
109
110 :param repo: repo instance
110 :param repo: repo instance
111 :param size: number of commits to show
111 :param size: number of commits to show
112 :param p: page number
112 :param p: page number
113 """
113 """
114 if not collection:
114 if not collection:
115 c.jsdata = json.dumps([])
115 c.jsdata = json.dumps([])
116 return
116 return
117
117
118 data = []
118 data = []
119 revs = [x.revision for x in collection]
119 revs = [x.revision for x in collection]
120
120
121 if repo.alias == 'git':
121 if repo.alias == 'git':
122 for _ in revs:
122 for _ in revs:
123 vtx = [0, 1]
123 vtx = [0, 1]
124 edges = [[0, 0, 1]]
124 edges = [[0, 0, 1]]
125 data.append(['', vtx, edges])
125 data.append(['', vtx, edges])
126
126
127 elif repo.alias == 'hg':
127 elif repo.alias == 'hg':
128 dag = graphmod.dagwalker(repo._repo, revs)
128 dag = graphmod.dagwalker(repo._repo, revs)
129 c.dag = graphmod.colored(dag, repo._repo)
129 c.dag = graphmod.colored(dag, repo._repo)
130 for (id, type, ctx, vtx, edges) in c.dag:
130 for (id, type, ctx, vtx, edges) in c.dag:
131 if type != graphmod.CHANGESET:
131 if type != graphmod.CHANGESET:
132 continue
132 continue
133 data.append(['', vtx, edges])
133 data.append(['', vtx, edges])
134
134
135 c.jsdata = json.dumps(data)
135 c.jsdata = json.dumps(data)
@@ -1,397 +1,403 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.controllers.changeset
3 rhodecode.controllers.changeset
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
5
5
6 changeset controller for pylons showoing changes beetween
6 changeset controller for pylons showoing changes beetween
7 revisions
7 revisions
8
8
9 :created_on: Apr 25, 2010
9 :created_on: Apr 25, 2010
10 :author: marcink
10 :author: marcink
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
11 :copyright: (C) 2010-2012 Marcin Kuzminski <marcin@python-works.com>
12 :license: GPLv3, see COPYING for more details.
12 :license: GPLv3, see COPYING for more details.
13 """
13 """
14 # This program is free software: you can redistribute it and/or modify
14 # This program is free software: you can redistribute it and/or modify
15 # it under the terms of the GNU General Public License as published by
15 # it under the terms of the GNU General Public License as published by
16 # the Free Software Foundation, either version 3 of the License, or
16 # the Free Software Foundation, either version 3 of the License, or
17 # (at your option) any later version.
17 # (at your option) any later version.
18 #
18 #
19 # This program is distributed in the hope that it will be useful,
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # GNU General Public License for more details.
22 # GNU General Public License for more details.
23 #
23 #
24 # You should have received a copy of the GNU General Public License
24 # You should have received a copy of the GNU General Public License
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
25 # along with this program. If not, see <http://www.gnu.org/licenses/>.
26 import logging
26 import logging
27 import traceback
27 import traceback
28 from collections import defaultdict
28 from collections import defaultdict
29 from webob.exc import HTTPForbidden
29 from webob.exc import HTTPForbidden
30
30
31 from pylons import tmpl_context as c, url, request, response
31 from pylons import tmpl_context as c, url, request, response
32 from pylons.i18n.translation import _
32 from pylons.i18n.translation import _
33 from pylons.controllers.util import redirect
33 from pylons.controllers.util import redirect
34 from pylons.decorators import jsonify
34 from pylons.decorators import jsonify
35
35
36 from rhodecode.lib.vcs.exceptions import RepositoryError, ChangesetError, \
36 from rhodecode.lib.vcs.exceptions import RepositoryError, ChangesetError, \
37 ChangesetDoesNotExistError
37 ChangesetDoesNotExistError
38 from rhodecode.lib.vcs.nodes import FileNode
38 from rhodecode.lib.vcs.nodes import FileNode
39
39
40 import rhodecode.lib.helpers as h
40 import rhodecode.lib.helpers as h
41 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
41 from rhodecode.lib.auth import LoginRequired, HasRepoPermissionAnyDecorator
42 from rhodecode.lib.base import BaseRepoController, render
42 from rhodecode.lib.base import BaseRepoController, render
43 from rhodecode.lib.utils import EmptyChangeset
43 from rhodecode.lib.utils import EmptyChangeset
44 from rhodecode.lib.compat import OrderedDict
44 from rhodecode.lib.compat import OrderedDict
45 from rhodecode.lib import diffs
45 from rhodecode.lib import diffs
46 from rhodecode.model.db import ChangesetComment
46 from rhodecode.model.db import ChangesetComment
47 from rhodecode.model.comment import ChangesetCommentsModel
47 from rhodecode.model.comment import ChangesetCommentsModel
48 from rhodecode.model.meta import Session
48 from rhodecode.model.meta import Session
49 from rhodecode.lib.diffs import wrapped_diff
49 from rhodecode.lib.diffs import wrapped_diff
50
50
51 log = logging.getLogger(__name__)
51 log = logging.getLogger(__name__)
52
52
53
53
54 def _update_with_GET(params, GET):
54 def _update_with_GET(params, GET):
55 for k in ['diff1', 'diff2', 'diff']:
55 for k in ['diff1', 'diff2', 'diff']:
56 params[k] += GET.getall(k)
56 params[k] += GET.getall(k)
57
57
58
58
59 def anchor_url(revision, path, GET):
59 def anchor_url(revision, path, GET):
60 fid = h.FID(revision, path)
60 fid = h.FID(revision, path)
61 return h.url.current(anchor=fid, **dict(GET))
61 return h.url.current(anchor=fid, **dict(GET))
62
62
63
63
64 def get_ignore_ws(fid, GET):
64 def get_ignore_ws(fid, GET):
65 ig_ws_global = GET.get('ignorews')
65 ig_ws_global = GET.get('ignorews')
66 ig_ws = filter(lambda k: k.startswith('WS'), GET.getall(fid))
66 ig_ws = filter(lambda k: k.startswith('WS'), GET.getall(fid))
67 if ig_ws:
67 if ig_ws:
68 try:
68 try:
69 return int(ig_ws[0].split(':')[-1])
69 return int(ig_ws[0].split(':')[-1])
70 except:
70 except:
71 pass
71 pass
72 return ig_ws_global
72 return ig_ws_global
73
73
74
74
75 def _ignorews_url(GET, fileid=None):
75 def _ignorews_url(GET, fileid=None):
76 fileid = str(fileid) if fileid else None
76 fileid = str(fileid) if fileid else None
77 params = defaultdict(list)
77 params = defaultdict(list)
78 _update_with_GET(params, GET)
78 _update_with_GET(params, GET)
79 lbl = _('show white space')
79 lbl = _('show white space')
80 ig_ws = get_ignore_ws(fileid, GET)
80 ig_ws = get_ignore_ws(fileid, GET)
81 ln_ctx = get_line_ctx(fileid, GET)
81 ln_ctx = get_line_ctx(fileid, GET)
82 # global option
82 # global option
83 if fileid is None:
83 if fileid is None:
84 if ig_ws is None:
84 if ig_ws is None:
85 params['ignorews'] += [1]
85 params['ignorews'] += [1]
86 lbl = _('ignore white space')
86 lbl = _('ignore white space')
87 ctx_key = 'context'
87 ctx_key = 'context'
88 ctx_val = ln_ctx
88 ctx_val = ln_ctx
89 # per file options
89 # per file options
90 else:
90 else:
91 if ig_ws is None:
91 if ig_ws is None:
92 params[fileid] += ['WS:1']
92 params[fileid] += ['WS:1']
93 lbl = _('ignore white space')
93 lbl = _('ignore white space')
94
94
95 ctx_key = fileid
95 ctx_key = fileid
96 ctx_val = 'C:%s' % ln_ctx
96 ctx_val = 'C:%s' % ln_ctx
97 # if we have passed in ln_ctx pass it along to our params
97 # if we have passed in ln_ctx pass it along to our params
98 if ln_ctx:
98 if ln_ctx:
99 params[ctx_key] += [ctx_val]
99 params[ctx_key] += [ctx_val]
100
100
101 params['anchor'] = fileid
101 params['anchor'] = fileid
102 img = h.image(h.url('/images/icons/text_strikethrough.png'), lbl, class_='icon')
102 img = h.image(h.url('/images/icons/text_strikethrough.png'), lbl, class_='icon')
103 return h.link_to(img, h.url.current(**params), title=lbl, class_='tooltip')
103 return h.link_to(img, h.url.current(**params), title=lbl, class_='tooltip')
104
104
105
105
106 def get_line_ctx(fid, GET):
106 def get_line_ctx(fid, GET):
107 ln_ctx_global = GET.get('context')
107 ln_ctx_global = GET.get('context')
108 ln_ctx = filter(lambda k: k.startswith('C'), GET.getall(fid))
108 ln_ctx = filter(lambda k: k.startswith('C'), GET.getall(fid))
109
109
110 if ln_ctx:
110 if ln_ctx:
111 retval = ln_ctx[0].split(':')[-1]
111 retval = ln_ctx[0].split(':')[-1]
112 else:
112 else:
113 retval = ln_ctx_global
113 retval = ln_ctx_global
114
114
115 try:
115 try:
116 return int(retval)
116 return int(retval)
117 except:
117 except:
118 return
118 return
119
119
120
120
121 def _context_url(GET, fileid=None):
121 def _context_url(GET, fileid=None):
122 """
122 """
123 Generates url for context lines
123 Generates url for context lines
124
124
125 :param fileid:
125 :param fileid:
126 """
126 """
127
127
128 fileid = str(fileid) if fileid else None
128 fileid = str(fileid) if fileid else None
129 ig_ws = get_ignore_ws(fileid, GET)
129 ig_ws = get_ignore_ws(fileid, GET)
130 ln_ctx = (get_line_ctx(fileid, GET) or 3) * 2
130 ln_ctx = (get_line_ctx(fileid, GET) or 3) * 2
131
131
132 params = defaultdict(list)
132 params = defaultdict(list)
133 _update_with_GET(params, GET)
133 _update_with_GET(params, GET)
134
134
135 # global option
135 # global option
136 if fileid is None:
136 if fileid is None:
137 if ln_ctx > 0:
137 if ln_ctx > 0:
138 params['context'] += [ln_ctx]
138 params['context'] += [ln_ctx]
139
139
140 if ig_ws:
140 if ig_ws:
141 ig_ws_key = 'ignorews'
141 ig_ws_key = 'ignorews'
142 ig_ws_val = 1
142 ig_ws_val = 1
143
143
144 # per file option
144 # per file option
145 else:
145 else:
146 params[fileid] += ['C:%s' % ln_ctx]
146 params[fileid] += ['C:%s' % ln_ctx]
147 ig_ws_key = fileid
147 ig_ws_key = fileid
148 ig_ws_val = 'WS:%s' % 1
148 ig_ws_val = 'WS:%s' % 1
149
149
150 if ig_ws:
150 if ig_ws:
151 params[ig_ws_key] += [ig_ws_val]
151 params[ig_ws_key] += [ig_ws_val]
152
152
153 lbl = _('%s line context') % ln_ctx
153 lbl = _('%s line context') % ln_ctx
154
154
155 params['anchor'] = fileid
155 params['anchor'] = fileid
156 img = h.image(h.url('/images/icons/table_add.png'), lbl, class_='icon')
156 img = h.image(h.url('/images/icons/table_add.png'), lbl, class_='icon')
157 return h.link_to(img, h.url.current(**params), title=lbl, class_='tooltip')
157 return h.link_to(img, h.url.current(**params), title=lbl, class_='tooltip')
158
158
159
159
160 class ChangesetController(BaseRepoController):
160 class ChangesetController(BaseRepoController):
161
161
162 @LoginRequired()
162 @LoginRequired()
163 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
163 @HasRepoPermissionAnyDecorator('repository.read', 'repository.write',
164 'repository.admin')
164 'repository.admin')
165 def __before__(self):
165 def __before__(self):
166 super(ChangesetController, self).__before__()
166 super(ChangesetController, self).__before__()
167 c.affected_files_cut_off = 60
167 c.affected_files_cut_off = 60
168
168
169 def index(self, revision):
169 def index(self, revision):
170
170
171 c.anchor_url = anchor_url
171 c.anchor_url = anchor_url
172 c.ignorews_url = _ignorews_url
172 c.ignorews_url = _ignorews_url
173 c.context_url = _context_url
173 c.context_url = _context_url
174 limit_off = request.GET.get('fulldiff')
174 limit_off = request.GET.get('fulldiff')
175 #get ranges of revisions if preset
175 #get ranges of revisions if preset
176 rev_range = revision.split('...')[:2]
176 rev_range = revision.split('...')[:2]
177 enable_comments = True
177 enable_comments = True
178 try:
178 try:
179 if len(rev_range) == 2:
179 if len(rev_range) == 2:
180 enable_comments = False
180 enable_comments = False
181 rev_start = rev_range[0]
181 rev_start = rev_range[0]
182 rev_end = rev_range[1]
182 rev_end = rev_range[1]
183 rev_ranges = c.rhodecode_repo.get_changesets(start=rev_start,
183 rev_ranges = c.rhodecode_repo.get_changesets(start=rev_start,
184 end=rev_end)
184 end=rev_end)
185 else:
185 else:
186 rev_ranges = [c.rhodecode_repo.get_changeset(revision)]
186 rev_ranges = [c.rhodecode_repo.get_changeset(revision)]
187
187
188 c.cs_ranges = list(rev_ranges)
188 c.cs_ranges = list(rev_ranges)
189 if not c.cs_ranges:
189 if not c.cs_ranges:
190 raise RepositoryError('Changeset range returned empty result')
190 raise RepositoryError('Changeset range returned empty result')
191
191
192 except (RepositoryError, ChangesetDoesNotExistError, Exception), e:
192 except (RepositoryError, ChangesetDoesNotExistError, Exception), e:
193 log.error(traceback.format_exc())
193 log.error(traceback.format_exc())
194 h.flash(str(e), category='warning')
194 h.flash(str(e), category='warning')
195 return redirect(url('home'))
195 return redirect(url('home'))
196
196
197 c.changes = OrderedDict()
197 c.changes = OrderedDict()
198
198
199 c.lines_added = 0 # count of lines added
199 c.lines_added = 0 # count of lines added
200 c.lines_deleted = 0 # count of lines removes
200 c.lines_deleted = 0 # count of lines removes
201
201
202 cumulative_diff = 0
202 cumulative_diff = 0
203 c.cut_off = False # defines if cut off limit is reached
203 c.cut_off = False # defines if cut off limit is reached
204
204
205 c.comments = []
205 c.comments = []
206 c.statuses = []
206 c.inline_comments = []
207 c.inline_comments = []
207 c.inline_cnt = 0
208 c.inline_cnt = 0
208 # Iterate over ranges (default changeset view is always one changeset)
209 # Iterate over ranges (default changeset view is always one changeset)
209 for changeset in c.cs_ranges:
210 for changeset in c.cs_ranges:
211
212 c.statuses.extend(ChangesetStatusModel()\
213 .get_status(c.rhodecode_db_repo.repo_id,
214 changeset.raw_id))
215
210 c.comments.extend(ChangesetCommentsModel()\
216 c.comments.extend(ChangesetCommentsModel()\
211 .get_comments(c.rhodecode_db_repo.repo_id,
217 .get_comments(c.rhodecode_db_repo.repo_id,
212 changeset.raw_id))
218 changeset.raw_id))
213 inlines = ChangesetCommentsModel()\
219 inlines = ChangesetCommentsModel()\
214 .get_inline_comments(c.rhodecode_db_repo.repo_id,
220 .get_inline_comments(c.rhodecode_db_repo.repo_id,
215 changeset.raw_id)
221 changeset.raw_id)
216 c.inline_comments.extend(inlines)
222 c.inline_comments.extend(inlines)
217 c.changes[changeset.raw_id] = []
223 c.changes[changeset.raw_id] = []
218 try:
224 try:
219 changeset_parent = changeset.parents[0]
225 changeset_parent = changeset.parents[0]
220 except IndexError:
226 except IndexError:
221 changeset_parent = None
227 changeset_parent = None
222
228
223 #==================================================================
229 #==================================================================
224 # ADDED FILES
230 # ADDED FILES
225 #==================================================================
231 #==================================================================
226 for node in changeset.added:
232 for node in changeset.added:
227 fid = h.FID(revision, node.path)
233 fid = h.FID(revision, node.path)
228 line_context_lcl = get_line_ctx(fid, request.GET)
234 line_context_lcl = get_line_ctx(fid, request.GET)
229 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
235 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
230 lim = self.cut_off_limit
236 lim = self.cut_off_limit
231 if cumulative_diff > self.cut_off_limit:
237 if cumulative_diff > self.cut_off_limit:
232 lim = -1 if limit_off is None else None
238 lim = -1 if limit_off is None else None
233 size, cs1, cs2, diff, st = wrapped_diff(
239 size, cs1, cs2, diff, st = wrapped_diff(
234 filenode_old=None,
240 filenode_old=None,
235 filenode_new=node,
241 filenode_new=node,
236 cut_off_limit=lim,
242 cut_off_limit=lim,
237 ignore_whitespace=ign_whitespace_lcl,
243 ignore_whitespace=ign_whitespace_lcl,
238 line_context=line_context_lcl,
244 line_context=line_context_lcl,
239 enable_comments=enable_comments
245 enable_comments=enable_comments
240 )
246 )
241 cumulative_diff += size
247 cumulative_diff += size
242 c.lines_added += st[0]
248 c.lines_added += st[0]
243 c.lines_deleted += st[1]
249 c.lines_deleted += st[1]
244 c.changes[changeset.raw_id].append(
250 c.changes[changeset.raw_id].append(
245 ('added', node, diff, cs1, cs2, st)
251 ('added', node, diff, cs1, cs2, st)
246 )
252 )
247
253
248 #==================================================================
254 #==================================================================
249 # CHANGED FILES
255 # CHANGED FILES
250 #==================================================================
256 #==================================================================
251 for node in changeset.changed:
257 for node in changeset.changed:
252 try:
258 try:
253 filenode_old = changeset_parent.get_node(node.path)
259 filenode_old = changeset_parent.get_node(node.path)
254 except ChangesetError:
260 except ChangesetError:
255 log.warning('Unable to fetch parent node for diff')
261 log.warning('Unable to fetch parent node for diff')
256 filenode_old = FileNode(node.path, '', EmptyChangeset())
262 filenode_old = FileNode(node.path, '', EmptyChangeset())
257
263
258 fid = h.FID(revision, node.path)
264 fid = h.FID(revision, node.path)
259 line_context_lcl = get_line_ctx(fid, request.GET)
265 line_context_lcl = get_line_ctx(fid, request.GET)
260 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
266 ign_whitespace_lcl = get_ignore_ws(fid, request.GET)
261 lim = self.cut_off_limit
267 lim = self.cut_off_limit
262 if cumulative_diff > self.cut_off_limit:
268 if cumulative_diff > self.cut_off_limit:
263 lim = -1 if limit_off is None else None
269 lim = -1 if limit_off is None else None
264 size, cs1, cs2, diff, st = wrapped_diff(
270 size, cs1, cs2, diff, st = wrapped_diff(
265 filenode_old=filenode_old,
271 filenode_old=filenode_old,
266 filenode_new=node,
272 filenode_new=node,
267 cut_off_limit=lim,
273 cut_off_limit=lim,
268 ignore_whitespace=ign_whitespace_lcl,
274 ignore_whitespace=ign_whitespace_lcl,
269 line_context=line_context_lcl,
275 line_context=line_context_lcl,
270 enable_comments=enable_comments
276 enable_comments=enable_comments
271 )
277 )
272 cumulative_diff += size
278 cumulative_diff += size
273 c.lines_added += st[0]
279 c.lines_added += st[0]
274 c.lines_deleted += st[1]
280 c.lines_deleted += st[1]
275 c.changes[changeset.raw_id].append(
281 c.changes[changeset.raw_id].append(
276 ('changed', node, diff, cs1, cs2, st)
282 ('changed', node, diff, cs1, cs2, st)
277 )
283 )
278 #==================================================================
284 #==================================================================
279 # REMOVED FILES
285 # REMOVED FILES
280 #==================================================================
286 #==================================================================
281 for node in changeset.removed:
287 for node in changeset.removed:
282 c.changes[changeset.raw_id].append(
288 c.changes[changeset.raw_id].append(
283 ('removed', node, None, None, None, (0, 0))
289 ('removed', node, None, None, None, (0, 0))
284 )
290 )
285
291
286 # count inline comments
292 # count inline comments
287 for path, lines in c.inline_comments:
293 for path, lines in c.inline_comments:
288 for comments in lines.values():
294 for comments in lines.values():
289 c.inline_cnt += len(comments)
295 c.inline_cnt += len(comments)
290
296
291 if len(c.cs_ranges) == 1:
297 if len(c.cs_ranges) == 1:
292 c.changeset = c.cs_ranges[0]
298 c.changeset = c.cs_ranges[0]
293 c.changes = c.changes[c.changeset.raw_id]
299 c.changes = c.changes[c.changeset.raw_id]
294
300
295 return render('changeset/changeset.html')
301 return render('changeset/changeset.html')
296 else:
302 else:
297 return render('changeset/changeset_range.html')
303 return render('changeset/changeset_range.html')
298
304
299 def raw_changeset(self, revision):
305 def raw_changeset(self, revision):
300
306
301 method = request.GET.get('diff', 'show')
307 method = request.GET.get('diff', 'show')
302 ignore_whitespace = request.GET.get('ignorews') == '1'
308 ignore_whitespace = request.GET.get('ignorews') == '1'
303 line_context = request.GET.get('context', 3)
309 line_context = request.GET.get('context', 3)
304 try:
310 try:
305 c.scm_type = c.rhodecode_repo.alias
311 c.scm_type = c.rhodecode_repo.alias
306 c.changeset = c.rhodecode_repo.get_changeset(revision)
312 c.changeset = c.rhodecode_repo.get_changeset(revision)
307 except RepositoryError:
313 except RepositoryError:
308 log.error(traceback.format_exc())
314 log.error(traceback.format_exc())
309 return redirect(url('home'))
315 return redirect(url('home'))
310 else:
316 else:
311 try:
317 try:
312 c.changeset_parent = c.changeset.parents[0]
318 c.changeset_parent = c.changeset.parents[0]
313 except IndexError:
319 except IndexError:
314 c.changeset_parent = None
320 c.changeset_parent = None
315 c.changes = []
321 c.changes = []
316
322
317 for node in c.changeset.added:
323 for node in c.changeset.added:
318 filenode_old = FileNode(node.path, '')
324 filenode_old = FileNode(node.path, '')
319 if filenode_old.is_binary or node.is_binary:
325 if filenode_old.is_binary or node.is_binary:
320 diff = _('binary file') + '\n'
326 diff = _('binary file') + '\n'
321 else:
327 else:
322 f_gitdiff = diffs.get_gitdiff(filenode_old, node,
328 f_gitdiff = diffs.get_gitdiff(filenode_old, node,
323 ignore_whitespace=ignore_whitespace,
329 ignore_whitespace=ignore_whitespace,
324 context=line_context)
330 context=line_context)
325 diff = diffs.DiffProcessor(f_gitdiff,
331 diff = diffs.DiffProcessor(f_gitdiff,
326 format='gitdiff').raw_diff()
332 format='gitdiff').raw_diff()
327
333
328 cs1 = None
334 cs1 = None
329 cs2 = node.changeset.raw_id
335 cs2 = node.changeset.raw_id
330 c.changes.append(('added', node, diff, cs1, cs2))
336 c.changes.append(('added', node, diff, cs1, cs2))
331
337
332 for node in c.changeset.changed:
338 for node in c.changeset.changed:
333 filenode_old = c.changeset_parent.get_node(node.path)
339 filenode_old = c.changeset_parent.get_node(node.path)
334 if filenode_old.is_binary or node.is_binary:
340 if filenode_old.is_binary or node.is_binary:
335 diff = _('binary file')
341 diff = _('binary file')
336 else:
342 else:
337 f_gitdiff = diffs.get_gitdiff(filenode_old, node,
343 f_gitdiff = diffs.get_gitdiff(filenode_old, node,
338 ignore_whitespace=ignore_whitespace,
344 ignore_whitespace=ignore_whitespace,
339 context=line_context)
345 context=line_context)
340 diff = diffs.DiffProcessor(f_gitdiff,
346 diff = diffs.DiffProcessor(f_gitdiff,
341 format='gitdiff').raw_diff()
347 format='gitdiff').raw_diff()
342
348
343 cs1 = filenode_old.changeset.raw_id
349 cs1 = filenode_old.changeset.raw_id
344 cs2 = node.changeset.raw_id
350 cs2 = node.changeset.raw_id
345 c.changes.append(('changed', node, diff, cs1, cs2))
351 c.changes.append(('changed', node, diff, cs1, cs2))
346
352
347 response.content_type = 'text/plain'
353 response.content_type = 'text/plain'
348
354
349 if method == 'download':
355 if method == 'download':
350 response.content_disposition = 'attachment; filename=%s.patch' \
356 response.content_disposition = 'attachment; filename=%s.patch' \
351 % revision
357 % revision
352
358
353 c.parent_tmpl = ''.join(['# Parent %s\n' % x.raw_id
359 c.parent_tmpl = ''.join(['# Parent %s\n' % x.raw_id
354 for x in c.changeset.parents])
360 for x in c.changeset.parents])
355
361
356 c.diffs = ''
362 c.diffs = ''
357 for x in c.changes:
363 for x in c.changes:
358 c.diffs += x[2]
364 c.diffs += x[2]
359
365
360 return render('changeset/raw_changeset.html')
366 return render('changeset/raw_changeset.html')
361
367
362 @jsonify
368 @jsonify
363 def comment(self, repo_name, revision):
369 def comment(self, repo_name, revision):
364 comm = ChangesetCommentsModel().create(
370 comm = ChangesetCommentsModel().create(
365 text=request.POST.get('text'),
371 text=request.POST.get('text'),
366 repo_id=c.rhodecode_db_repo.repo_id,
372 repo_id=c.rhodecode_db_repo.repo_id,
367 user_id=c.rhodecode_user.user_id,
373 user_id=c.rhodecode_user.user_id,
368 revision=revision,
374 revision=revision,
369 f_path=request.POST.get('f_path'),
375 f_path=request.POST.get('f_path'),
370 line_no=request.POST.get('line')
376 line_no=request.POST.get('line')
371 )
377 )
372 Session.commit()
378 Session.commit()
373 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
379 if not request.environ.get('HTTP_X_PARTIAL_XHR'):
374 return redirect(h.url('changeset_home', repo_name=repo_name,
380 return redirect(h.url('changeset_home', repo_name=repo_name,
375 revision=revision))
381 revision=revision))
376
382
377 data = {
383 data = {
378 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
384 'target_id': h.safeid(h.safe_unicode(request.POST.get('f_path'))),
379 }
385 }
380 if comm:
386 if comm:
381 c.co = comm
387 c.co = comm
382 data.update(comm.get_dict())
388 data.update(comm.get_dict())
383 data.update({'rendered_text':
389 data.update({'rendered_text':
384 render('changeset/changeset_comment_block.html')})
390 render('changeset/changeset_comment_block.html')})
385
391
386 return data
392 return data
387
393
388 @jsonify
394 @jsonify
389 def delete_comment(self, repo_name, comment_id):
395 def delete_comment(self, repo_name, comment_id):
390 co = ChangesetComment.get(comment_id)
396 co = ChangesetComment.get(comment_id)
391 owner = lambda: co.author.user_id == c.rhodecode_user.user_id
397 owner = lambda: co.author.user_id == c.rhodecode_user.user_id
392 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
398 if h.HasPermissionAny('hg.admin', 'repository.admin')() or owner:
393 ChangesetCommentsModel().delete(comment=co)
399 ChangesetCommentsModel().delete(comment=co)
394 Session.commit()
400 Session.commit()
395 return True
401 return True
396 else:
402 else:
397 raise HTTPForbidden()
403 raise HTTPForbidden()
@@ -1,53 +1,60 b''
1 from __future__ import with_statement
1 from __future__ import with_statement
2
2
3 import gc
4 import objgraph
3 import cProfile
5 import cProfile
4 import pstats
6 import pstats
5 import cgi
7 import cgi
6 import pprint
8 import pprint
7 import threading
9 import threading
8
10
9 from StringIO import StringIO
11 from StringIO import StringIO
10
12
11
13
12 class ProfilingMiddleware(object):
14 class ProfilingMiddleware(object):
13 def __init__(self, app):
15 def __init__(self, app):
14 self.lock = threading.Lock()
16 self.lock = threading.Lock()
15 self.app = app
17 self.app = app
16
18
17 def __call__(self, environ, start_response):
19 def __call__(self, environ, start_response):
18 with self.lock:
20 with self.lock:
19 profiler = cProfile.Profile()
21 profiler = cProfile.Profile()
20
22
21 def run_app(*a, **kw):
23 def run_app(*a, **kw):
22 self.response = self.app(environ, start_response)
24 self.response = self.app(environ, start_response)
23
25
24 profiler.runcall(run_app, environ, start_response)
26 profiler.runcall(run_app, environ, start_response)
25
27
26 profiler.snapshot_stats()
28 profiler.snapshot_stats()
27
29
28 stats = pstats.Stats(profiler)
30 stats = pstats.Stats(profiler)
29 stats.sort_stats('cumulative')
31 stats.sort_stats('calls') #cummulative
30
32
31 # Redirect output
33 # Redirect output
32 out = StringIO()
34 out = StringIO()
33 stats.stream = out
35 stats.stream = out
34
36
35 stats.print_stats()
37 stats.print_stats()
36
38
37 resp = ''.join(self.response)
39 resp = ''.join(self.response)
38
40
39 # Lets at least only put this on html-like responses.
41 # Lets at least only put this on html-like responses.
40 if resp.strip().startswith('<'):
42 if resp.strip().startswith('<'):
41 ## The profiling info is just appended to the response.
43 ## The profiling info is just appended to the response.
42 ## Browsers don't mind this.
44 ## Browsers don't mind this.
43 resp += ('<pre style="text-align:left; '
45 resp += ('<pre style="text-align:left; '
44 'border-top: 4px dashed red; padding: 1em;">')
46 'border-top: 4px dashed red; padding: 1em;">')
45 resp += cgi.escape(out.getvalue(), True)
47 resp += cgi.escape(out.getvalue(), True)
46
48
49 ct = objgraph.show_most_common_types()
50 print ct
51
52 resp += ct if ct else '---'
53
47 output = StringIO()
54 output = StringIO()
48 pprint.pprint(environ, output, depth=3)
55 pprint.pprint(environ, output, depth=3)
49
56
50 resp += cgi.escape(output.getvalue(), True)
57 resp += cgi.escape(output.getvalue(), True)
51 resp += '</pre>'
58 resp += '</pre>'
52
59
53 return resp
60 return resp
@@ -1,1292 +1,1349 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """
2 """
3 rhodecode.model.db
3 rhodecode.model.db
4 ~~~~~~~~~~~~~~~~~~
4 ~~~~~~~~~~~~~~~~~~
5
5
6 Database Models for RhodeCode
6 Database Models for RhodeCode
7
7
8 :created_on: Apr 08, 2010
8 :created_on: Apr 08, 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 logging
27 import logging
28 import datetime
28 import datetime
29 import traceback
29 import traceback
30 from collections import defaultdict
30 from collections import defaultdict
31
31
32 from sqlalchemy import *
32 from sqlalchemy import *
33 from sqlalchemy.ext.hybrid import hybrid_property
33 from sqlalchemy.ext.hybrid import hybrid_property
34 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
34 from sqlalchemy.orm import relationship, joinedload, class_mapper, validates
35 from beaker.cache import cache_region, region_invalidate
35 from beaker.cache import cache_region, region_invalidate
36
36
37 _ = lambda s: s
38
37 from rhodecode.lib.vcs import get_backend
39 from rhodecode.lib.vcs import get_backend
38 from rhodecode.lib.vcs.utils.helpers import get_scm
40 from rhodecode.lib.vcs.utils.helpers import get_scm
39 from rhodecode.lib.vcs.exceptions import VCSError
41 from rhodecode.lib.vcs.exceptions import VCSError
40 from rhodecode.lib.vcs.utils.lazy import LazyProperty
42 from rhodecode.lib.vcs.utils.lazy import LazyProperty
41
43
42 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
44 from rhodecode.lib.utils2 import str2bool, safe_str, get_changeset_safe, \
43 safe_unicode
45 safe_unicode
44 from rhodecode.lib.compat import json
46 from rhodecode.lib.compat import json
45 from rhodecode.lib.caching_query import FromCache
47 from rhodecode.lib.caching_query import FromCache
46
48
47 from rhodecode.model.meta import Base, Session
49 from rhodecode.model.meta import Base, Session
48 import hashlib
50 import hashlib
49
51
50
52
51 log = logging.getLogger(__name__)
53 log = logging.getLogger(__name__)
52
54
53 #==============================================================================
55 #==============================================================================
54 # BASE CLASSES
56 # BASE CLASSES
55 #==============================================================================
57 #==============================================================================
56
58
57 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
59 _hash_key = lambda k: hashlib.md5(safe_str(k)).hexdigest()
58
60
59
61
60 class ModelSerializer(json.JSONEncoder):
62 class ModelSerializer(json.JSONEncoder):
61 """
63 """
62 Simple Serializer for JSON,
64 Simple Serializer for JSON,
63
65
64 usage::
66 usage::
65
67
66 to make object customized for serialization implement a __json__
68 to make object customized for serialization implement a __json__
67 method that will return a dict for serialization into json
69 method that will return a dict for serialization into json
68
70
69 example::
71 example::
70
72
71 class Task(object):
73 class Task(object):
72
74
73 def __init__(self, name, value):
75 def __init__(self, name, value):
74 self.name = name
76 self.name = name
75 self.value = value
77 self.value = value
76
78
77 def __json__(self):
79 def __json__(self):
78 return dict(name=self.name,
80 return dict(name=self.name,
79 value=self.value)
81 value=self.value)
80
82
81 """
83 """
82
84
83 def default(self, obj):
85 def default(self, obj):
84
86
85 if hasattr(obj, '__json__'):
87 if hasattr(obj, '__json__'):
86 return obj.__json__()
88 return obj.__json__()
87 else:
89 else:
88 return json.JSONEncoder.default(self, obj)
90 return json.JSONEncoder.default(self, obj)
89
91
90
92
91 class BaseModel(object):
93 class BaseModel(object):
92 """
94 """
93 Base Model for all classess
95 Base Model for all classess
94 """
96 """
95
97
96 @classmethod
98 @classmethod
97 def _get_keys(cls):
99 def _get_keys(cls):
98 """return column names for this model """
100 """return column names for this model """
99 return class_mapper(cls).c.keys()
101 return class_mapper(cls).c.keys()
100
102
101 def get_dict(self):
103 def get_dict(self):
102 """
104 """
103 return dict with keys and values corresponding
105 return dict with keys and values corresponding
104 to this model data """
106 to this model data """
105
107
106 d = {}
108 d = {}
107 for k in self._get_keys():
109 for k in self._get_keys():
108 d[k] = getattr(self, k)
110 d[k] = getattr(self, k)
109
111
110 # also use __json__() if present to get additional fields
112 # also use __json__() if present to get additional fields
111 for k, val in getattr(self, '__json__', lambda: {})().iteritems():
113 for k, val in getattr(self, '__json__', lambda: {})().iteritems():
112 d[k] = val
114 d[k] = val
113 return d
115 return d
114
116
115 def get_appstruct(self):
117 def get_appstruct(self):
116 """return list with keys and values tupples corresponding
118 """return list with keys and values tupples corresponding
117 to this model data """
119 to this model data """
118
120
119 l = []
121 l = []
120 for k in self._get_keys():
122 for k in self._get_keys():
121 l.append((k, getattr(self, k),))
123 l.append((k, getattr(self, k),))
122 return l
124 return l
123
125
124 def populate_obj(self, populate_dict):
126 def populate_obj(self, populate_dict):
125 """populate model with data from given populate_dict"""
127 """populate model with data from given populate_dict"""
126
128
127 for k in self._get_keys():
129 for k in self._get_keys():
128 if k in populate_dict:
130 if k in populate_dict:
129 setattr(self, k, populate_dict[k])
131 setattr(self, k, populate_dict[k])
130
132
131 @classmethod
133 @classmethod
132 def query(cls):
134 def query(cls):
133 return Session.query(cls)
135 return Session.query(cls)
134
136
135 @classmethod
137 @classmethod
136 def get(cls, id_):
138 def get(cls, id_):
137 if id_:
139 if id_:
138 return cls.query().get(id_)
140 return cls.query().get(id_)
139
141
140 @classmethod
142 @classmethod
141 def getAll(cls):
143 def getAll(cls):
142 return cls.query().all()
144 return cls.query().all()
143
145
144 @classmethod
146 @classmethod
145 def delete(cls, id_):
147 def delete(cls, id_):
146 obj = cls.query().get(id_)
148 obj = cls.query().get(id_)
147 Session.delete(obj)
149 Session.delete(obj)
148
150
149 def __repr__(self):
151 def __repr__(self):
150 if hasattr(self, '__unicode__'):
152 if hasattr(self, '__unicode__'):
151 # python repr needs to return str
153 # python repr needs to return str
152 return safe_str(self.__unicode__())
154 return safe_str(self.__unicode__())
153 return '<DB:%s>' % (self.__class__.__name__)
155 return '<DB:%s>' % (self.__class__.__name__)
154
156
155 class RhodeCodeSetting(Base, BaseModel):
157 class RhodeCodeSetting(Base, BaseModel):
156 __tablename__ = 'rhodecode_settings'
158 __tablename__ = 'rhodecode_settings'
157 __table_args__ = (
159 __table_args__ = (
158 UniqueConstraint('app_settings_name'),
160 UniqueConstraint('app_settings_name'),
159 {'extend_existing': True, 'mysql_engine':'InnoDB',
161 {'extend_existing': True, 'mysql_engine': 'InnoDB',
160 'mysql_charset': 'utf8'}
162 'mysql_charset': 'utf8'}
161 )
163 )
162 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
164 app_settings_id = Column("app_settings_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
163 app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
165 app_settings_name = Column("app_settings_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
164 _app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
166 _app_settings_value = Column("app_settings_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
165
167
166 def __init__(self, k='', v=''):
168 def __init__(self, k='', v=''):
167 self.app_settings_name = k
169 self.app_settings_name = k
168 self.app_settings_value = v
170 self.app_settings_value = v
169
171
170 @validates('_app_settings_value')
172 @validates('_app_settings_value')
171 def validate_settings_value(self, key, val):
173 def validate_settings_value(self, key, val):
172 assert type(val) == unicode
174 assert type(val) == unicode
173 return val
175 return val
174
176
175 @hybrid_property
177 @hybrid_property
176 def app_settings_value(self):
178 def app_settings_value(self):
177 v = self._app_settings_value
179 v = self._app_settings_value
178 if self.app_settings_name == 'ldap_active':
180 if self.app_settings_name == 'ldap_active':
179 v = str2bool(v)
181 v = str2bool(v)
180 return v
182 return v
181
183
182 @app_settings_value.setter
184 @app_settings_value.setter
183 def app_settings_value(self, val):
185 def app_settings_value(self, val):
184 """
186 """
185 Setter that will always make sure we use unicode in app_settings_value
187 Setter that will always make sure we use unicode in app_settings_value
186
188
187 :param val:
189 :param val:
188 """
190 """
189 self._app_settings_value = safe_unicode(val)
191 self._app_settings_value = safe_unicode(val)
190
192
191 def __unicode__(self):
193 def __unicode__(self):
192 return u"<%s('%s:%s')>" % (
194 return u"<%s('%s:%s')>" % (
193 self.__class__.__name__,
195 self.__class__.__name__,
194 self.app_settings_name, self.app_settings_value
196 self.app_settings_name, self.app_settings_value
195 )
197 )
196
198
197 @classmethod
199 @classmethod
198 def get_by_name(cls, ldap_key):
200 def get_by_name(cls, ldap_key):
199 return cls.query()\
201 return cls.query()\
200 .filter(cls.app_settings_name == ldap_key).scalar()
202 .filter(cls.app_settings_name == ldap_key).scalar()
201
203
202 @classmethod
204 @classmethod
203 def get_app_settings(cls, cache=False):
205 def get_app_settings(cls, cache=False):
204
206
205 ret = cls.query()
207 ret = cls.query()
206
208
207 if cache:
209 if cache:
208 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
210 ret = ret.options(FromCache("sql_cache_short", "get_hg_settings"))
209
211
210 if not ret:
212 if not ret:
211 raise Exception('Could not get application settings !')
213 raise Exception('Could not get application settings !')
212 settings = {}
214 settings = {}
213 for each in ret:
215 for each in ret:
214 settings['rhodecode_' + each.app_settings_name] = \
216 settings['rhodecode_' + each.app_settings_name] = \
215 each.app_settings_value
217 each.app_settings_value
216
218
217 return settings
219 return settings
218
220
219 @classmethod
221 @classmethod
220 def get_ldap_settings(cls, cache=False):
222 def get_ldap_settings(cls, cache=False):
221 ret = cls.query()\
223 ret = cls.query()\
222 .filter(cls.app_settings_name.startswith('ldap_')).all()
224 .filter(cls.app_settings_name.startswith('ldap_')).all()
223 fd = {}
225 fd = {}
224 for row in ret:
226 for row in ret:
225 fd.update({row.app_settings_name:row.app_settings_value})
227 fd.update({row.app_settings_name:row.app_settings_value})
226
228
227 return fd
229 return fd
228
230
229
231
230 class RhodeCodeUi(Base, BaseModel):
232 class RhodeCodeUi(Base, BaseModel):
231 __tablename__ = 'rhodecode_ui'
233 __tablename__ = 'rhodecode_ui'
232 __table_args__ = (
234 __table_args__ = (
233 UniqueConstraint('ui_key'),
235 UniqueConstraint('ui_key'),
234 {'extend_existing': True, 'mysql_engine':'InnoDB',
236 {'extend_existing': True, 'mysql_engine': 'InnoDB',
235 'mysql_charset': 'utf8'}
237 'mysql_charset': 'utf8'}
236 )
238 )
237
239
238 HOOK_UPDATE = 'changegroup.update'
240 HOOK_UPDATE = 'changegroup.update'
239 HOOK_REPO_SIZE = 'changegroup.repo_size'
241 HOOK_REPO_SIZE = 'changegroup.repo_size'
240 HOOK_PUSH = 'pretxnchangegroup.push_logger'
242 HOOK_PUSH = 'pretxnchangegroup.push_logger'
241 HOOK_PULL = 'preoutgoing.pull_logger'
243 HOOK_PULL = 'preoutgoing.pull_logger'
242
244
243 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
245 ui_id = Column("ui_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
244 ui_section = Column("ui_section", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
246 ui_section = Column("ui_section", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
245 ui_key = Column("ui_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
247 ui_key = Column("ui_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
246 ui_value = Column("ui_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
248 ui_value = Column("ui_value", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
247 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
249 ui_active = Column("ui_active", Boolean(), nullable=True, unique=None, default=True)
248
250
249 @classmethod
251 @classmethod
250 def get_by_key(cls, key):
252 def get_by_key(cls, key):
251 return cls.query().filter(cls.ui_key == key)
253 return cls.query().filter(cls.ui_key == key)
252
254
253 @classmethod
255 @classmethod
254 def get_builtin_hooks(cls):
256 def get_builtin_hooks(cls):
255 q = cls.query()
257 q = cls.query()
256 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE,
258 q = q.filter(cls.ui_key.in_([cls.HOOK_UPDATE,
257 cls.HOOK_REPO_SIZE,
259 cls.HOOK_REPO_SIZE,
258 cls.HOOK_PUSH, cls.HOOK_PULL]))
260 cls.HOOK_PUSH, cls.HOOK_PULL]))
259 return q.all()
261 return q.all()
260
262
261 @classmethod
263 @classmethod
262 def get_custom_hooks(cls):
264 def get_custom_hooks(cls):
263 q = cls.query()
265 q = cls.query()
264 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE,
266 q = q.filter(~cls.ui_key.in_([cls.HOOK_UPDATE,
265 cls.HOOK_REPO_SIZE,
267 cls.HOOK_REPO_SIZE,
266 cls.HOOK_PUSH, cls.HOOK_PULL]))
268 cls.HOOK_PUSH, cls.HOOK_PULL]))
267 q = q.filter(cls.ui_section == 'hooks')
269 q = q.filter(cls.ui_section == 'hooks')
268 return q.all()
270 return q.all()
269
271
270 @classmethod
272 @classmethod
271 def create_or_update_hook(cls, key, val):
273 def create_or_update_hook(cls, key, val):
272 new_ui = cls.get_by_key(key).scalar() or cls()
274 new_ui = cls.get_by_key(key).scalar() or cls()
273 new_ui.ui_section = 'hooks'
275 new_ui.ui_section = 'hooks'
274 new_ui.ui_active = True
276 new_ui.ui_active = True
275 new_ui.ui_key = key
277 new_ui.ui_key = key
276 new_ui.ui_value = val
278 new_ui.ui_value = val
277
279
278 Session.add(new_ui)
280 Session.add(new_ui)
279
281
280
282
281 class User(Base, BaseModel):
283 class User(Base, BaseModel):
282 __tablename__ = 'users'
284 __tablename__ = 'users'
283 __table_args__ = (
285 __table_args__ = (
284 UniqueConstraint('username'), UniqueConstraint('email'),
286 UniqueConstraint('username'), UniqueConstraint('email'),
285 {'extend_existing': True, 'mysql_engine':'InnoDB',
287 {'extend_existing': True, 'mysql_engine': 'InnoDB',
286 'mysql_charset': 'utf8'}
288 'mysql_charset': 'utf8'}
287 )
289 )
288 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
290 user_id = Column("user_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
289 username = Column("username", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
291 username = Column("username", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
290 password = Column("password", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
292 password = Column("password", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
291 active = Column("active", Boolean(), nullable=True, unique=None, default=None)
293 active = Column("active", Boolean(), nullable=True, unique=None, default=None)
292 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
294 admin = Column("admin", Boolean(), nullable=True, unique=None, default=False)
293 name = Column("name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
295 name = Column("name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
294 lastname = Column("lastname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
296 lastname = Column("lastname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
295 _email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
297 _email = Column("email", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
296 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
298 last_login = Column("last_login", DateTime(timezone=False), nullable=True, unique=None, default=None)
297 ldap_dn = Column("ldap_dn", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
299 ldap_dn = Column("ldap_dn", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
298 api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
300 api_key = Column("api_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
299
301
300 user_log = relationship('UserLog', cascade='all')
302 user_log = relationship('UserLog', cascade='all')
301 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
303 user_perms = relationship('UserToPerm', primaryjoin="User.user_id==UserToPerm.user_id", cascade='all')
302
304
303 repositories = relationship('Repository')
305 repositories = relationship('Repository')
304 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
306 user_followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_user_id==User.user_id', cascade='all')
305 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
307 repo_to_perm = relationship('UserRepoToPerm', primaryjoin='UserRepoToPerm.user_id==User.user_id', cascade='all')
306 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
308 repo_group_to_perm = relationship('UserRepoGroupToPerm', primaryjoin='UserRepoGroupToPerm.user_id==User.user_id', cascade='all')
307
309
308 group_member = relationship('UsersGroupMember', cascade='all')
310 group_member = relationship('UsersGroupMember', cascade='all')
309
311
310 notifications = relationship('UserNotification', cascade='all')
312 notifications = relationship('UserNotification', cascade='all')
311 # notifications assigned to this user
313 # notifications assigned to this user
312 user_created_notifications = relationship('Notification', cascade='all')
314 user_created_notifications = relationship('Notification', cascade='all')
313 # comments created by this user
315 # comments created by this user
314 user_comments = relationship('ChangesetComment', cascade='all')
316 user_comments = relationship('ChangesetComment', cascade='all')
315
317
316 @hybrid_property
318 @hybrid_property
317 def email(self):
319 def email(self):
318 return self._email
320 return self._email
319
321
320 @email.setter
322 @email.setter
321 def email(self, val):
323 def email(self, val):
322 self._email = val.lower() if val else None
324 self._email = val.lower() if val else None
323
325
324 @property
326 @property
325 def full_name(self):
327 def full_name(self):
326 return '%s %s' % (self.name, self.lastname)
328 return '%s %s' % (self.name, self.lastname)
327
329
328 @property
330 @property
329 def full_name_or_username(self):
331 def full_name_or_username(self):
330 return ('%s %s' % (self.name, self.lastname)
332 return ('%s %s' % (self.name, self.lastname)
331 if (self.name and self.lastname) else self.username)
333 if (self.name and self.lastname) else self.username)
332
334
333 @property
335 @property
334 def full_contact(self):
336 def full_contact(self):
335 return '%s %s <%s>' % (self.name, self.lastname, self.email)
337 return '%s %s <%s>' % (self.name, self.lastname, self.email)
336
338
337 @property
339 @property
338 def short_contact(self):
340 def short_contact(self):
339 return '%s %s' % (self.name, self.lastname)
341 return '%s %s' % (self.name, self.lastname)
340
342
341 @property
343 @property
342 def is_admin(self):
344 def is_admin(self):
343 return self.admin
345 return self.admin
344
346
345 def __unicode__(self):
347 def __unicode__(self):
346 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
348 return u"<%s('id:%s:%s')>" % (self.__class__.__name__,
347 self.user_id, self.username)
349 self.user_id, self.username)
348
350
349 @classmethod
351 @classmethod
350 def get_by_username(cls, username, case_insensitive=False, cache=False):
352 def get_by_username(cls, username, case_insensitive=False, cache=False):
351 if case_insensitive:
353 if case_insensitive:
352 q = cls.query().filter(cls.username.ilike(username))
354 q = cls.query().filter(cls.username.ilike(username))
353 else:
355 else:
354 q = cls.query().filter(cls.username == username)
356 q = cls.query().filter(cls.username == username)
355
357
356 if cache:
358 if cache:
357 q = q.options(FromCache(
359 q = q.options(FromCache(
358 "sql_cache_short",
360 "sql_cache_short",
359 "get_user_%s" % _hash_key(username)
361 "get_user_%s" % _hash_key(username)
360 )
362 )
361 )
363 )
362 return q.scalar()
364 return q.scalar()
363
365
364 @classmethod
366 @classmethod
365 def get_by_api_key(cls, api_key, cache=False):
367 def get_by_api_key(cls, api_key, cache=False):
366 q = cls.query().filter(cls.api_key == api_key)
368 q = cls.query().filter(cls.api_key == api_key)
367
369
368 if cache:
370 if cache:
369 q = q.options(FromCache("sql_cache_short",
371 q = q.options(FromCache("sql_cache_short",
370 "get_api_key_%s" % api_key))
372 "get_api_key_%s" % api_key))
371 return q.scalar()
373 return q.scalar()
372
374
373 @classmethod
375 @classmethod
374 def get_by_email(cls, email, case_insensitive=False, cache=False):
376 def get_by_email(cls, email, case_insensitive=False, cache=False):
375 if case_insensitive:
377 if case_insensitive:
376 q = cls.query().filter(cls.email.ilike(email))
378 q = cls.query().filter(cls.email.ilike(email))
377 else:
379 else:
378 q = cls.query().filter(cls.email == email)
380 q = cls.query().filter(cls.email == email)
379
381
380 if cache:
382 if cache:
381 q = q.options(FromCache("sql_cache_short",
383 q = q.options(FromCache("sql_cache_short",
382 "get_api_key_%s" % email))
384 "get_api_key_%s" % email))
383 return q.scalar()
385 return q.scalar()
384
386
385 def update_lastlogin(self):
387 def update_lastlogin(self):
386 """Update user lastlogin"""
388 """Update user lastlogin"""
387 self.last_login = datetime.datetime.now()
389 self.last_login = datetime.datetime.now()
388 Session.add(self)
390 Session.add(self)
389 log.debug('updated user %s lastlogin' % self.username)
391 log.debug('updated user %s lastlogin' % self.username)
390
392
391 def __json__(self):
393 def __json__(self):
392 return dict(
394 return dict(
393 user_id=self.user_id,
395 user_id=self.user_id,
394 first_name=self.name,
396 first_name=self.name,
395 last_name=self.lastname,
397 last_name=self.lastname,
396 email=self.email,
398 email=self.email,
397 full_name=self.full_name,
399 full_name=self.full_name,
398 full_name_or_username=self.full_name_or_username,
400 full_name_or_username=self.full_name_or_username,
399 short_contact=self.short_contact,
401 short_contact=self.short_contact,
400 full_contact=self.full_contact
402 full_contact=self.full_contact
401 )
403 )
402
404
403
405
404 class UserLog(Base, BaseModel):
406 class UserLog(Base, BaseModel):
405 __tablename__ = 'user_logs'
407 __tablename__ = 'user_logs'
406 __table_args__ = (
408 __table_args__ = (
407 {'extend_existing': True, 'mysql_engine':'InnoDB',
409 {'extend_existing': True, 'mysql_engine': 'InnoDB',
408 'mysql_charset': 'utf8'},
410 'mysql_charset': 'utf8'},
409 )
411 )
410 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
412 user_log_id = Column("user_log_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
411 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
413 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
412 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
414 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True)
413 repository_name = Column("repository_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
415 repository_name = Column("repository_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
414 user_ip = Column("user_ip", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
416 user_ip = Column("user_ip", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
415 action = Column("action", UnicodeText(length=1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
417 action = Column("action", UnicodeText(length=1200000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
416 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
418 action_date = Column("action_date", DateTime(timezone=False), nullable=True, unique=None, default=None)
417
419
418 @property
420 @property
419 def action_as_day(self):
421 def action_as_day(self):
420 return datetime.date(*self.action_date.timetuple()[:3])
422 return datetime.date(*self.action_date.timetuple()[:3])
421
423
422 user = relationship('User')
424 user = relationship('User')
423 repository = relationship('Repository', cascade='')
425 repository = relationship('Repository', cascade='')
424
426
425
427
426 class UsersGroup(Base, BaseModel):
428 class UsersGroup(Base, BaseModel):
427 __tablename__ = 'users_groups'
429 __tablename__ = 'users_groups'
428 __table_args__ = (
430 __table_args__ = (
429 {'extend_existing': True, 'mysql_engine':'InnoDB',
431 {'extend_existing': True, 'mysql_engine': 'InnoDB',
430 'mysql_charset': 'utf8'},
432 'mysql_charset': 'utf8'},
431 )
433 )
432
434
433 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
435 users_group_id = Column("users_group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
434 users_group_name = Column("users_group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
436 users_group_name = Column("users_group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
435 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
437 users_group_active = Column("users_group_active", Boolean(), nullable=True, unique=None, default=None)
436
438
437 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
439 members = relationship('UsersGroupMember', cascade="all, delete, delete-orphan", lazy="joined")
438 users_group_to_perm = relationship('UsersGroupToPerm', cascade='all')
440 users_group_to_perm = relationship('UsersGroupToPerm', cascade='all')
439 users_group_repo_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
441 users_group_repo_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
440
442
441 def __unicode__(self):
443 def __unicode__(self):
442 return u'<userGroup(%s)>' % (self.users_group_name)
444 return u'<userGroup(%s)>' % (self.users_group_name)
443
445
444 @classmethod
446 @classmethod
445 def get_by_group_name(cls, group_name, cache=False,
447 def get_by_group_name(cls, group_name, cache=False,
446 case_insensitive=False):
448 case_insensitive=False):
447 if case_insensitive:
449 if case_insensitive:
448 q = cls.query().filter(cls.users_group_name.ilike(group_name))
450 q = cls.query().filter(cls.users_group_name.ilike(group_name))
449 else:
451 else:
450 q = cls.query().filter(cls.users_group_name == group_name)
452 q = cls.query().filter(cls.users_group_name == group_name)
451 if cache:
453 if cache:
452 q = q.options(FromCache(
454 q = q.options(FromCache(
453 "sql_cache_short",
455 "sql_cache_short",
454 "get_user_%s" % _hash_key(group_name)
456 "get_user_%s" % _hash_key(group_name)
455 )
457 )
456 )
458 )
457 return q.scalar()
459 return q.scalar()
458
460
459 @classmethod
461 @classmethod
460 def get(cls, users_group_id, cache=False):
462 def get(cls, users_group_id, cache=False):
461 users_group = cls.query()
463 users_group = cls.query()
462 if cache:
464 if cache:
463 users_group = users_group.options(FromCache("sql_cache_short",
465 users_group = users_group.options(FromCache("sql_cache_short",
464 "get_users_group_%s" % users_group_id))
466 "get_users_group_%s" % users_group_id))
465 return users_group.get(users_group_id)
467 return users_group.get(users_group_id)
466
468
467
469
468 class UsersGroupMember(Base, BaseModel):
470 class UsersGroupMember(Base, BaseModel):
469 __tablename__ = 'users_groups_members'
471 __tablename__ = 'users_groups_members'
470 __table_args__ = (
472 __table_args__ = (
471 {'extend_existing': True, 'mysql_engine':'InnoDB',
473 {'extend_existing': True, 'mysql_engine': 'InnoDB',
472 'mysql_charset': 'utf8'},
474 'mysql_charset': 'utf8'},
473 )
475 )
474
476
475 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
477 users_group_member_id = Column("users_group_member_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
476 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
478 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
477 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
479 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
478
480
479 user = relationship('User', lazy='joined')
481 user = relationship('User', lazy='joined')
480 users_group = relationship('UsersGroup')
482 users_group = relationship('UsersGroup')
481
483
482 def __init__(self, gr_id='', u_id=''):
484 def __init__(self, gr_id='', u_id=''):
483 self.users_group_id = gr_id
485 self.users_group_id = gr_id
484 self.user_id = u_id
486 self.user_id = u_id
485
487
486
488
487 class Repository(Base, BaseModel):
489 class Repository(Base, BaseModel):
488 __tablename__ = 'repositories'
490 __tablename__ = 'repositories'
489 __table_args__ = (
491 __table_args__ = (
490 UniqueConstraint('repo_name'),
492 UniqueConstraint('repo_name'),
491 {'extend_existing': True, 'mysql_engine':'InnoDB',
493 {'extend_existing': True, 'mysql_engine': 'InnoDB',
492 'mysql_charset': 'utf8'},
494 'mysql_charset': 'utf8'},
493 )
495 )
494
496
495 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
497 repo_id = Column("repo_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
496 repo_name = Column("repo_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
498 repo_name = Column("repo_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
497 clone_uri = Column("clone_uri", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
499 clone_uri = Column("clone_uri", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=False, default=None)
498 repo_type = Column("repo_type", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default='hg')
500 repo_type = Column("repo_type", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=False, default='hg')
499 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
501 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=False, default=None)
500 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
502 private = Column("private", Boolean(), nullable=True, unique=None, default=None)
501 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
503 enable_statistics = Column("statistics", Boolean(), nullable=True, unique=None, default=True)
502 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
504 enable_downloads = Column("downloads", Boolean(), nullable=True, unique=None, default=True)
503 description = Column("description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
505 description = Column("description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
504 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
506 created_on = Column('created_on', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
505
507
506 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
508 fork_id = Column("fork_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=False, default=None)
507 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
509 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=False, default=None)
508
510
509 user = relationship('User')
511 user = relationship('User')
510 fork = relationship('Repository', remote_side=repo_id)
512 fork = relationship('Repository', remote_side=repo_id)
511 group = relationship('RepoGroup')
513 group = relationship('RepoGroup')
512 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
514 repo_to_perm = relationship('UserRepoToPerm', cascade='all', order_by='UserRepoToPerm.repo_to_perm_id')
513 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
515 users_group_to_perm = relationship('UsersGroupRepoToPerm', cascade='all')
514 stats = relationship('Statistics', cascade='all', uselist=False)
516 stats = relationship('Statistics', cascade='all', uselist=False)
515
517
516 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
518 followers = relationship('UserFollowing', primaryjoin='UserFollowing.follows_repo_id==Repository.repo_id', cascade='all')
517
519
518 logs = relationship('UserLog')
520 logs = relationship('UserLog')
519
521
520 def __unicode__(self):
522 def __unicode__(self):
521 return u"<%s('%s:%s')>" % (self.__class__.__name__,self.repo_id,
523 return u"<%s('%s:%s')>" % (self.__class__.__name__,self.repo_id,
522 self.repo_name)
524 self.repo_name)
523
525
524 @classmethod
526 @classmethod
525 def url_sep(cls):
527 def url_sep(cls):
526 return '/'
528 return '/'
527
529
528 @classmethod
530 @classmethod
529 def get_by_repo_name(cls, repo_name):
531 def get_by_repo_name(cls, repo_name):
530 q = Session.query(cls).filter(cls.repo_name == repo_name)
532 q = Session.query(cls).filter(cls.repo_name == repo_name)
531 q = q.options(joinedload(Repository.fork))\
533 q = q.options(joinedload(Repository.fork))\
532 .options(joinedload(Repository.user))\
534 .options(joinedload(Repository.user))\
533 .options(joinedload(Repository.group))
535 .options(joinedload(Repository.group))
534 return q.scalar()
536 return q.scalar()
535
537
536 @classmethod
538 @classmethod
537 def get_repo_forks(cls, repo_id):
539 def get_repo_forks(cls, repo_id):
538 return cls.query().filter(Repository.fork_id == repo_id)
540 return cls.query().filter(Repository.fork_id == repo_id)
539
541
540 @classmethod
542 @classmethod
541 def base_path(cls):
543 def base_path(cls):
542 """
544 """
543 Returns base path when all repos are stored
545 Returns base path when all repos are stored
544
546
545 :param cls:
547 :param cls:
546 """
548 """
547 q = Session.query(RhodeCodeUi)\
549 q = Session.query(RhodeCodeUi)\
548 .filter(RhodeCodeUi.ui_key == cls.url_sep())
550 .filter(RhodeCodeUi.ui_key == cls.url_sep())
549 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
551 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
550 return q.one().ui_value
552 return q.one().ui_value
551
553
552 @property
554 @property
553 def just_name(self):
555 def just_name(self):
554 return self.repo_name.split(Repository.url_sep())[-1]
556 return self.repo_name.split(Repository.url_sep())[-1]
555
557
556 @property
558 @property
557 def groups_with_parents(self):
559 def groups_with_parents(self):
558 groups = []
560 groups = []
559 if self.group is None:
561 if self.group is None:
560 return groups
562 return groups
561
563
562 cur_gr = self.group
564 cur_gr = self.group
563 groups.insert(0, cur_gr)
565 groups.insert(0, cur_gr)
564 while 1:
566 while 1:
565 gr = getattr(cur_gr, 'parent_group', None)
567 gr = getattr(cur_gr, 'parent_group', None)
566 cur_gr = cur_gr.parent_group
568 cur_gr = cur_gr.parent_group
567 if gr is None:
569 if gr is None:
568 break
570 break
569 groups.insert(0, gr)
571 groups.insert(0, gr)
570
572
571 return groups
573 return groups
572
574
573 @property
575 @property
574 def groups_and_repo(self):
576 def groups_and_repo(self):
575 return self.groups_with_parents, self.just_name
577 return self.groups_with_parents, self.just_name
576
578
577 @LazyProperty
579 @LazyProperty
578 def repo_path(self):
580 def repo_path(self):
579 """
581 """
580 Returns base full path for that repository means where it actually
582 Returns base full path for that repository means where it actually
581 exists on a filesystem
583 exists on a filesystem
582 """
584 """
583 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
585 q = Session.query(RhodeCodeUi).filter(RhodeCodeUi.ui_key ==
584 Repository.url_sep())
586 Repository.url_sep())
585 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
587 q = q.options(FromCache("sql_cache_short", "repository_repo_path"))
586 return q.one().ui_value
588 return q.one().ui_value
587
589
588 @property
590 @property
589 def repo_full_path(self):
591 def repo_full_path(self):
590 p = [self.repo_path]
592 p = [self.repo_path]
591 # we need to split the name by / since this is how we store the
593 # we need to split the name by / since this is how we store the
592 # names in the database, but that eventually needs to be converted
594 # names in the database, but that eventually needs to be converted
593 # into a valid system path
595 # into a valid system path
594 p += self.repo_name.split(Repository.url_sep())
596 p += self.repo_name.split(Repository.url_sep())
595 return os.path.join(*p)
597 return os.path.join(*p)
596
598
597 def get_new_name(self, repo_name):
599 def get_new_name(self, repo_name):
598 """
600 """
599 returns new full repository name based on assigned group and new new
601 returns new full repository name based on assigned group and new new
600
602
601 :param group_name:
603 :param group_name:
602 """
604 """
603 path_prefix = self.group.full_path_splitted if self.group else []
605 path_prefix = self.group.full_path_splitted if self.group else []
604 return Repository.url_sep().join(path_prefix + [repo_name])
606 return Repository.url_sep().join(path_prefix + [repo_name])
605
607
606 @property
608 @property
607 def _ui(self):
609 def _ui(self):
608 """
610 """
609 Creates an db based ui object for this repository
611 Creates an db based ui object for this repository
610 """
612 """
611 from mercurial import ui
613 from mercurial import ui
612 from mercurial import config
614 from mercurial import config
613 baseui = ui.ui()
615 baseui = ui.ui()
614
616
615 #clean the baseui object
617 #clean the baseui object
616 baseui._ocfg = config.config()
618 baseui._ocfg = config.config()
617 baseui._ucfg = config.config()
619 baseui._ucfg = config.config()
618 baseui._tcfg = config.config()
620 baseui._tcfg = config.config()
619
621
620 ret = RhodeCodeUi.query()\
622 ret = RhodeCodeUi.query()\
621 .options(FromCache("sql_cache_short", "repository_repo_ui")).all()
623 .options(FromCache("sql_cache_short", "repository_repo_ui")).all()
622
624
623 hg_ui = ret
625 hg_ui = ret
624 for ui_ in hg_ui:
626 for ui_ in hg_ui:
625 if ui_.ui_active:
627 if ui_.ui_active:
626 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
628 log.debug('settings ui from db[%s]%s:%s', ui_.ui_section,
627 ui_.ui_key, ui_.ui_value)
629 ui_.ui_key, ui_.ui_value)
628 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
630 baseui.setconfig(ui_.ui_section, ui_.ui_key, ui_.ui_value)
629
631
630 return baseui
632 return baseui
631
633
632 @classmethod
634 @classmethod
633 def is_valid(cls, repo_name):
635 def is_valid(cls, repo_name):
634 """
636 """
635 returns True if given repo name is a valid filesystem repository
637 returns True if given repo name is a valid filesystem repository
636
638
637 :param cls:
639 :param cls:
638 :param repo_name:
640 :param repo_name:
639 """
641 """
640 from rhodecode.lib.utils import is_valid_repo
642 from rhodecode.lib.utils import is_valid_repo
641
643
642 return is_valid_repo(repo_name, cls.base_path())
644 return is_valid_repo(repo_name, cls.base_path())
643
645
644 #==========================================================================
646 #==========================================================================
645 # SCM PROPERTIES
647 # SCM PROPERTIES
646 #==========================================================================
648 #==========================================================================
647
649
648 def get_changeset(self, rev):
650 def get_changeset(self, rev):
649 return get_changeset_safe(self.scm_instance, rev)
651 return get_changeset_safe(self.scm_instance, rev)
650
652
651 @property
653 @property
652 def tip(self):
654 def tip(self):
653 return self.get_changeset('tip')
655 return self.get_changeset('tip')
654
656
655 @property
657 @property
656 def author(self):
658 def author(self):
657 return self.tip.author
659 return self.tip.author
658
660
659 @property
661 @property
660 def last_change(self):
662 def last_change(self):
661 return self.scm_instance.last_change
663 return self.scm_instance.last_change
662
664
663 def comments(self, revisions=None):
665 def comments(self, revisions=None):
664 """
666 """
665 Returns comments for this repository grouped by revisions
667 Returns comments for this repository grouped by revisions
666
668
667 :param revisions: filter query by revisions only
669 :param revisions: filter query by revisions only
668 """
670 """
669 cmts = ChangesetComment.query()\
671 cmts = ChangesetComment.query()\
670 .filter(ChangesetComment.repo == self)
672 .filter(ChangesetComment.repo == self)
671 if revisions:
673 if revisions:
672 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
674 cmts = cmts.filter(ChangesetComment.revision.in_(revisions))
673 grouped = defaultdict(list)
675 grouped = defaultdict(list)
674 for cmt in cmts.all():
676 for cmt in cmts.all():
675 grouped[cmt.revision].append(cmt)
677 grouped[cmt.revision].append(cmt)
676 return grouped
678 return grouped
677
679
680 def statuses(self, revisions=None):
681 """
682 Returns statuses for this repository
683 :param revisions: list of revisions to get statuses for
684 :type revisions: list
685 """
686
687 statuses = ChangesetStatus.query()\
688 .filter(ChangesetStatus.repo == self)
689 if revisions:
690 statuses = statuses.filter(ChangesetStatus.revision.in_(revisions))
691 grouped = defaultdict(list)
692 for stat in statuses.all():
693 grouped[statuses.revision].append(stat)
694 return grouped
695
696
678 #==========================================================================
697 #==========================================================================
679 # SCM CACHE INSTANCE
698 # SCM CACHE INSTANCE
680 #==========================================================================
699 #==========================================================================
681
700
682 @property
701 @property
683 def invalidate(self):
702 def invalidate(self):
684 return CacheInvalidation.invalidate(self.repo_name)
703 return CacheInvalidation.invalidate(self.repo_name)
685
704
686 def set_invalidate(self):
705 def set_invalidate(self):
687 """
706 """
688 set a cache for invalidation for this instance
707 set a cache for invalidation for this instance
689 """
708 """
690 CacheInvalidation.set_invalidate(self.repo_name)
709 CacheInvalidation.set_invalidate(self.repo_name)
691
710
692 @LazyProperty
711 @LazyProperty
693 def scm_instance(self):
712 def scm_instance(self):
694 return self.__get_instance()
713 return self.__get_instance()
695
714
696 @property
715 @property
697 def scm_instance_cached(self):
716 def scm_instance_cached(self):
698 @cache_region('long_term')
717 @cache_region('long_term')
699 def _c(repo_name):
718 def _c(repo_name):
700 return self.__get_instance()
719 return self.__get_instance()
701 rn = self.repo_name
720 rn = self.repo_name
702 log.debug('Getting cached instance of repo')
721 log.debug('Getting cached instance of repo')
703 inv = self.invalidate
722 inv = self.invalidate
704 if inv is not None:
723 if inv is not None:
705 region_invalidate(_c, None, rn)
724 region_invalidate(_c, None, rn)
706 # update our cache
725 # update our cache
707 CacheInvalidation.set_valid(inv.cache_key)
726 CacheInvalidation.set_valid(inv.cache_key)
708 return _c(rn)
727 return _c(rn)
709
728
710 def __get_instance(self):
729 def __get_instance(self):
711 repo_full_path = self.repo_full_path
730 repo_full_path = self.repo_full_path
712 try:
731 try:
713 alias = get_scm(repo_full_path)[0]
732 alias = get_scm(repo_full_path)[0]
714 log.debug('Creating instance of %s repository' % alias)
733 log.debug('Creating instance of %s repository' % alias)
715 backend = get_backend(alias)
734 backend = get_backend(alias)
716 except VCSError:
735 except VCSError:
717 log.error(traceback.format_exc())
736 log.error(traceback.format_exc())
718 log.error('Perhaps this repository is in db and not in '
737 log.error('Perhaps this repository is in db and not in '
719 'filesystem run rescan repositories with '
738 'filesystem run rescan repositories with '
720 '"destroy old data " option from admin panel')
739 '"destroy old data " option from admin panel')
721 return
740 return
722
741
723 if alias == 'hg':
742 if alias == 'hg':
724
743
725 repo = backend(safe_str(repo_full_path), create=False,
744 repo = backend(safe_str(repo_full_path), create=False,
726 baseui=self._ui)
745 baseui=self._ui)
727 # skip hidden web repository
746 # skip hidden web repository
728 if repo._get_hidden():
747 if repo._get_hidden():
729 return
748 return
730 else:
749 else:
731 repo = backend(repo_full_path, create=False)
750 repo = backend(repo_full_path, create=False)
732
751
733 return repo
752 return repo
734
753
735
754
736 class RepoGroup(Base, BaseModel):
755 class RepoGroup(Base, BaseModel):
737 __tablename__ = 'groups'
756 __tablename__ = 'groups'
738 __table_args__ = (
757 __table_args__ = (
739 UniqueConstraint('group_name', 'group_parent_id'),
758 UniqueConstraint('group_name', 'group_parent_id'),
740 CheckConstraint('group_id != group_parent_id'),
759 CheckConstraint('group_id != group_parent_id'),
741 {'extend_existing': True, 'mysql_engine':'InnoDB',
760 {'extend_existing': True, 'mysql_engine': 'InnoDB',
742 'mysql_charset': 'utf8'},
761 'mysql_charset': 'utf8'},
743 )
762 )
744 __mapper_args__ = {'order_by': 'group_name'}
763 __mapper_args__ = {'order_by': 'group_name'}
745
764
746 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
765 group_id = Column("group_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
747 group_name = Column("group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
766 group_name = Column("group_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=False, unique=True, default=None)
748 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
767 group_parent_id = Column("group_parent_id", Integer(), ForeignKey('groups.group_id'), nullable=True, unique=None, default=None)
749 group_description = Column("group_description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
768 group_description = Column("group_description", String(length=10000, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
750
769
751 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
770 repo_group_to_perm = relationship('UserRepoGroupToPerm', cascade='all', order_by='UserRepoGroupToPerm.group_to_perm_id')
752 users_group_to_perm = relationship('UsersGroupRepoGroupToPerm', cascade='all')
771 users_group_to_perm = relationship('UsersGroupRepoGroupToPerm', cascade='all')
753
772
754 parent_group = relationship('RepoGroup', remote_side=group_id)
773 parent_group = relationship('RepoGroup', remote_side=group_id)
755
774
756 def __init__(self, group_name='', parent_group=None):
775 def __init__(self, group_name='', parent_group=None):
757 self.group_name = group_name
776 self.group_name = group_name
758 self.parent_group = parent_group
777 self.parent_group = parent_group
759
778
760 def __unicode__(self):
779 def __unicode__(self):
761 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
780 return u"<%s('%s:%s')>" % (self.__class__.__name__, self.group_id,
762 self.group_name)
781 self.group_name)
763
782
764 @classmethod
783 @classmethod
765 def groups_choices(cls):
784 def groups_choices(cls):
766 from webhelpers.html import literal as _literal
785 from webhelpers.html import literal as _literal
767 repo_groups = [('', '')]
786 repo_groups = [('', '')]
768 sep = ' &raquo; '
787 sep = ' &raquo; '
769 _name = lambda k: _literal(sep.join(k))
788 _name = lambda k: _literal(sep.join(k))
770
789
771 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
790 repo_groups.extend([(x.group_id, _name(x.full_path_splitted))
772 for x in cls.query().all()])
791 for x in cls.query().all()])
773
792
774 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
793 repo_groups = sorted(repo_groups, key=lambda t: t[1].split(sep)[0])
775 return repo_groups
794 return repo_groups
776
795
777 @classmethod
796 @classmethod
778 def url_sep(cls):
797 def url_sep(cls):
779 return '/'
798 return '/'
780
799
781 @classmethod
800 @classmethod
782 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
801 def get_by_group_name(cls, group_name, cache=False, case_insensitive=False):
783 if case_insensitive:
802 if case_insensitive:
784 gr = cls.query()\
803 gr = cls.query()\
785 .filter(cls.group_name.ilike(group_name))
804 .filter(cls.group_name.ilike(group_name))
786 else:
805 else:
787 gr = cls.query()\
806 gr = cls.query()\
788 .filter(cls.group_name == group_name)
807 .filter(cls.group_name == group_name)
789 if cache:
808 if cache:
790 gr = gr.options(FromCache(
809 gr = gr.options(FromCache(
791 "sql_cache_short",
810 "sql_cache_short",
792 "get_group_%s" % _hash_key(group_name)
811 "get_group_%s" % _hash_key(group_name)
793 )
812 )
794 )
813 )
795 return gr.scalar()
814 return gr.scalar()
796
815
797 @property
816 @property
798 def parents(self):
817 def parents(self):
799 parents_recursion_limit = 5
818 parents_recursion_limit = 5
800 groups = []
819 groups = []
801 if self.parent_group is None:
820 if self.parent_group is None:
802 return groups
821 return groups
803 cur_gr = self.parent_group
822 cur_gr = self.parent_group
804 groups.insert(0, cur_gr)
823 groups.insert(0, cur_gr)
805 cnt = 0
824 cnt = 0
806 while 1:
825 while 1:
807 cnt += 1
826 cnt += 1
808 gr = getattr(cur_gr, 'parent_group', None)
827 gr = getattr(cur_gr, 'parent_group', None)
809 cur_gr = cur_gr.parent_group
828 cur_gr = cur_gr.parent_group
810 if gr is None:
829 if gr is None:
811 break
830 break
812 if cnt == parents_recursion_limit:
831 if cnt == parents_recursion_limit:
813 # this will prevent accidental infinit loops
832 # this will prevent accidental infinit loops
814 log.error('group nested more than %s' %
833 log.error('group nested more than %s' %
815 parents_recursion_limit)
834 parents_recursion_limit)
816 break
835 break
817
836
818 groups.insert(0, gr)
837 groups.insert(0, gr)
819 return groups
838 return groups
820
839
821 @property
840 @property
822 def children(self):
841 def children(self):
823 return RepoGroup.query().filter(RepoGroup.parent_group == self)
842 return RepoGroup.query().filter(RepoGroup.parent_group == self)
824
843
825 @property
844 @property
826 def name(self):
845 def name(self):
827 return self.group_name.split(RepoGroup.url_sep())[-1]
846 return self.group_name.split(RepoGroup.url_sep())[-1]
828
847
829 @property
848 @property
830 def full_path(self):
849 def full_path(self):
831 return self.group_name
850 return self.group_name
832
851
833 @property
852 @property
834 def full_path_splitted(self):
853 def full_path_splitted(self):
835 return self.group_name.split(RepoGroup.url_sep())
854 return self.group_name.split(RepoGroup.url_sep())
836
855
837 @property
856 @property
838 def repositories(self):
857 def repositories(self):
839 return Repository.query()\
858 return Repository.query()\
840 .filter(Repository.group == self)\
859 .filter(Repository.group == self)\
841 .order_by(Repository.repo_name)
860 .order_by(Repository.repo_name)
842
861
843 @property
862 @property
844 def repositories_recursive_count(self):
863 def repositories_recursive_count(self):
845 cnt = self.repositories.count()
864 cnt = self.repositories.count()
846
865
847 def children_count(group):
866 def children_count(group):
848 cnt = 0
867 cnt = 0
849 for child in group.children:
868 for child in group.children:
850 cnt += child.repositories.count()
869 cnt += child.repositories.count()
851 cnt += children_count(child)
870 cnt += children_count(child)
852 return cnt
871 return cnt
853
872
854 return cnt + children_count(self)
873 return cnt + children_count(self)
855
874
856 def get_new_name(self, group_name):
875 def get_new_name(self, group_name):
857 """
876 """
858 returns new full group name based on parent and new name
877 returns new full group name based on parent and new name
859
878
860 :param group_name:
879 :param group_name:
861 """
880 """
862 path_prefix = (self.parent_group.full_path_splitted if
881 path_prefix = (self.parent_group.full_path_splitted if
863 self.parent_group else [])
882 self.parent_group else [])
864 return RepoGroup.url_sep().join(path_prefix + [group_name])
883 return RepoGroup.url_sep().join(path_prefix + [group_name])
865
884
866
885
867 class Permission(Base, BaseModel):
886 class Permission(Base, BaseModel):
868 __tablename__ = 'permissions'
887 __tablename__ = 'permissions'
869 __table_args__ = (
888 __table_args__ = (
870 {'extend_existing': True, 'mysql_engine':'InnoDB',
889 {'extend_existing': True, 'mysql_engine': 'InnoDB',
871 'mysql_charset': 'utf8'},
890 'mysql_charset': 'utf8'},
872 )
891 )
873 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
892 permission_id = Column("permission_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
874 permission_name = Column("permission_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
893 permission_name = Column("permission_name", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
875 permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
894 permission_longname = Column("permission_longname", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
876
895
877 def __unicode__(self):
896 def __unicode__(self):
878 return u"<%s('%s:%s')>" % (
897 return u"<%s('%s:%s')>" % (
879 self.__class__.__name__, self.permission_id, self.permission_name
898 self.__class__.__name__, self.permission_id, self.permission_name
880 )
899 )
881
900
882 @classmethod
901 @classmethod
883 def get_by_key(cls, key):
902 def get_by_key(cls, key):
884 return cls.query().filter(cls.permission_name == key).scalar()
903 return cls.query().filter(cls.permission_name == key).scalar()
885
904
886 @classmethod
905 @classmethod
887 def get_default_perms(cls, default_user_id):
906 def get_default_perms(cls, default_user_id):
888 q = Session.query(UserRepoToPerm, Repository, cls)\
907 q = Session.query(UserRepoToPerm, Repository, cls)\
889 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
908 .join((Repository, UserRepoToPerm.repository_id == Repository.repo_id))\
890 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
909 .join((cls, UserRepoToPerm.permission_id == cls.permission_id))\
891 .filter(UserRepoToPerm.user_id == default_user_id)
910 .filter(UserRepoToPerm.user_id == default_user_id)
892
911
893 return q.all()
912 return q.all()
894
913
895 @classmethod
914 @classmethod
896 def get_default_group_perms(cls, default_user_id):
915 def get_default_group_perms(cls, default_user_id):
897 q = Session.query(UserRepoGroupToPerm, RepoGroup, cls)\
916 q = Session.query(UserRepoGroupToPerm, RepoGroup, cls)\
898 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
917 .join((RepoGroup, UserRepoGroupToPerm.group_id == RepoGroup.group_id))\
899 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
918 .join((cls, UserRepoGroupToPerm.permission_id == cls.permission_id))\
900 .filter(UserRepoGroupToPerm.user_id == default_user_id)
919 .filter(UserRepoGroupToPerm.user_id == default_user_id)
901
920
902 return q.all()
921 return q.all()
903
922
904
923
905 class UserRepoToPerm(Base, BaseModel):
924 class UserRepoToPerm(Base, BaseModel):
906 __tablename__ = 'repo_to_perm'
925 __tablename__ = 'repo_to_perm'
907 __table_args__ = (
926 __table_args__ = (
908 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
927 UniqueConstraint('user_id', 'repository_id', 'permission_id'),
909 {'extend_existing': True, 'mysql_engine':'InnoDB',
928 {'extend_existing': True, 'mysql_engine': 'InnoDB',
910 'mysql_charset': 'utf8'}
929 'mysql_charset': 'utf8'}
911 )
930 )
912 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
931 repo_to_perm_id = Column("repo_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
913 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
932 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
914 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
933 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
915 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
934 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
916
935
917 user = relationship('User')
936 user = relationship('User')
918 repository = relationship('Repository')
937 repository = relationship('Repository')
919 permission = relationship('Permission')
938 permission = relationship('Permission')
920
939
921 @classmethod
940 @classmethod
922 def create(cls, user, repository, permission):
941 def create(cls, user, repository, permission):
923 n = cls()
942 n = cls()
924 n.user = user
943 n.user = user
925 n.repository = repository
944 n.repository = repository
926 n.permission = permission
945 n.permission = permission
927 Session.add(n)
946 Session.add(n)
928 return n
947 return n
929
948
930 def __unicode__(self):
949 def __unicode__(self):
931 return u'<user:%s => %s >' % (self.user, self.repository)
950 return u'<user:%s => %s >' % (self.user, self.repository)
932
951
933
952
934 class UserToPerm(Base, BaseModel):
953 class UserToPerm(Base, BaseModel):
935 __tablename__ = 'user_to_perm'
954 __tablename__ = 'user_to_perm'
936 __table_args__ = (
955 __table_args__ = (
937 UniqueConstraint('user_id', 'permission_id'),
956 UniqueConstraint('user_id', 'permission_id'),
938 {'extend_existing': True, 'mysql_engine':'InnoDB',
957 {'extend_existing': True, 'mysql_engine': 'InnoDB',
939 'mysql_charset': 'utf8'}
958 'mysql_charset': 'utf8'}
940 )
959 )
941 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
960 user_to_perm_id = Column("user_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
942 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
961 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
943 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
962 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
944
963
945 user = relationship('User')
964 user = relationship('User')
946 permission = relationship('Permission', lazy='joined')
965 permission = relationship('Permission', lazy='joined')
947
966
948
967
949 class UsersGroupRepoToPerm(Base, BaseModel):
968 class UsersGroupRepoToPerm(Base, BaseModel):
950 __tablename__ = 'users_group_repo_to_perm'
969 __tablename__ = 'users_group_repo_to_perm'
951 __table_args__ = (
970 __table_args__ = (
952 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
971 UniqueConstraint('repository_id', 'users_group_id', 'permission_id'),
953 {'extend_existing': True, 'mysql_engine':'InnoDB',
972 {'extend_existing': True, 'mysql_engine': 'InnoDB',
954 'mysql_charset': 'utf8'}
973 'mysql_charset': 'utf8'}
955 )
974 )
956 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
975 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
957 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
976 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
958 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
977 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
959 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
978 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=None, default=None)
960
979
961 users_group = relationship('UsersGroup')
980 users_group = relationship('UsersGroup')
962 permission = relationship('Permission')
981 permission = relationship('Permission')
963 repository = relationship('Repository')
982 repository = relationship('Repository')
964
983
965 @classmethod
984 @classmethod
966 def create(cls, users_group, repository, permission):
985 def create(cls, users_group, repository, permission):
967 n = cls()
986 n = cls()
968 n.users_group = users_group
987 n.users_group = users_group
969 n.repository = repository
988 n.repository = repository
970 n.permission = permission
989 n.permission = permission
971 Session.add(n)
990 Session.add(n)
972 return n
991 return n
973
992
974 def __unicode__(self):
993 def __unicode__(self):
975 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
994 return u'<userGroup:%s => %s >' % (self.users_group, self.repository)
976
995
977
996
978 class UsersGroupToPerm(Base, BaseModel):
997 class UsersGroupToPerm(Base, BaseModel):
979 __tablename__ = 'users_group_to_perm'
998 __tablename__ = 'users_group_to_perm'
980 __table_args__ = (
999 __table_args__ = (
981 UniqueConstraint('users_group_id', 'permission_id',),
1000 UniqueConstraint('users_group_id', 'permission_id',),
982 {'extend_existing': True, 'mysql_engine':'InnoDB',
1001 {'extend_existing': True, 'mysql_engine': 'InnoDB',
983 'mysql_charset': 'utf8'}
1002 'mysql_charset': 'utf8'}
984 )
1003 )
985 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1004 users_group_to_perm_id = Column("users_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
986 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1005 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
987 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1006 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
988
1007
989 users_group = relationship('UsersGroup')
1008 users_group = relationship('UsersGroup')
990 permission = relationship('Permission')
1009 permission = relationship('Permission')
991
1010
992
1011
993 class UserRepoGroupToPerm(Base, BaseModel):
1012 class UserRepoGroupToPerm(Base, BaseModel):
994 __tablename__ = 'user_repo_group_to_perm'
1013 __tablename__ = 'user_repo_group_to_perm'
995 __table_args__ = (
1014 __table_args__ = (
996 UniqueConstraint('user_id', 'group_id', 'permission_id'),
1015 UniqueConstraint('user_id', 'group_id', 'permission_id'),
997 {'extend_existing': True, 'mysql_engine':'InnoDB',
1016 {'extend_existing': True, 'mysql_engine': 'InnoDB',
998 'mysql_charset': 'utf8'}
1017 'mysql_charset': 'utf8'}
999 )
1018 )
1000
1019
1001 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1020 group_to_perm_id = Column("group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1002 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1021 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1003 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1022 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1004 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1023 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1005
1024
1006 user = relationship('User')
1025 user = relationship('User')
1007 group = relationship('RepoGroup')
1026 group = relationship('RepoGroup')
1008 permission = relationship('Permission')
1027 permission = relationship('Permission')
1009
1028
1010
1029
1011 class UsersGroupRepoGroupToPerm(Base, BaseModel):
1030 class UsersGroupRepoGroupToPerm(Base, BaseModel):
1012 __tablename__ = 'users_group_repo_group_to_perm'
1031 __tablename__ = 'users_group_repo_group_to_perm'
1013 __table_args__ = (
1032 __table_args__ = (
1014 UniqueConstraint('users_group_id', 'group_id'),
1033 UniqueConstraint('users_group_id', 'group_id'),
1015 {'extend_existing': True, 'mysql_engine':'InnoDB',
1034 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1016 'mysql_charset': 'utf8'}
1035 'mysql_charset': 'utf8'}
1017 )
1036 )
1018
1037
1019 users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1038 users_group_repo_group_to_perm_id = Column("users_group_repo_group_to_perm_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1020 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1039 users_group_id = Column("users_group_id", Integer(), ForeignKey('users_groups.users_group_id'), nullable=False, unique=None, default=None)
1021 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1040 group_id = Column("group_id", Integer(), ForeignKey('groups.group_id'), nullable=False, unique=None, default=None)
1022 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1041 permission_id = Column("permission_id", Integer(), ForeignKey('permissions.permission_id'), nullable=False, unique=None, default=None)
1023
1042
1024 users_group = relationship('UsersGroup')
1043 users_group = relationship('UsersGroup')
1025 permission = relationship('Permission')
1044 permission = relationship('Permission')
1026 group = relationship('RepoGroup')
1045 group = relationship('RepoGroup')
1027
1046
1028
1047
1029 class Statistics(Base, BaseModel):
1048 class Statistics(Base, BaseModel):
1030 __tablename__ = 'statistics'
1049 __tablename__ = 'statistics'
1031 __table_args__ = (
1050 __table_args__ = (
1032 UniqueConstraint('repository_id'),
1051 UniqueConstraint('repository_id'),
1033 {'extend_existing': True, 'mysql_engine':'InnoDB',
1052 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1034 'mysql_charset': 'utf8'}
1053 'mysql_charset': 'utf8'}
1035 )
1054 )
1036 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1055 stat_id = Column("stat_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1037 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1056 repository_id = Column("repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=False, unique=True, default=None)
1038 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1057 stat_on_revision = Column("stat_on_revision", Integer(), nullable=False)
1039 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1058 commit_activity = Column("commit_activity", LargeBinary(1000000), nullable=False)#JSON data
1040 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1059 commit_activity_combined = Column("commit_activity_combined", LargeBinary(), nullable=False)#JSON data
1041 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1060 languages = Column("languages", LargeBinary(1000000), nullable=False)#JSON data
1042
1061
1043 repository = relationship('Repository', single_parent=True)
1062 repository = relationship('Repository', single_parent=True)
1044
1063
1045
1064
1046 class UserFollowing(Base, BaseModel):
1065 class UserFollowing(Base, BaseModel):
1047 __tablename__ = 'user_followings'
1066 __tablename__ = 'user_followings'
1048 __table_args__ = (
1067 __table_args__ = (
1049 UniqueConstraint('user_id', 'follows_repository_id'),
1068 UniqueConstraint('user_id', 'follows_repository_id'),
1050 UniqueConstraint('user_id', 'follows_user_id'),
1069 UniqueConstraint('user_id', 'follows_user_id'),
1051 {'extend_existing': True, 'mysql_engine':'InnoDB',
1070 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1052 'mysql_charset': 'utf8'}
1071 'mysql_charset': 'utf8'}
1053 )
1072 )
1054
1073
1055 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1074 user_following_id = Column("user_following_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1056 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1075 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None, default=None)
1057 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1076 follows_repo_id = Column("follows_repository_id", Integer(), ForeignKey('repositories.repo_id'), nullable=True, unique=None, default=None)
1058 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1077 follows_user_id = Column("follows_user_id", Integer(), ForeignKey('users.user_id'), nullable=True, unique=None, default=None)
1059 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1078 follows_from = Column('follows_from', DateTime(timezone=False), nullable=True, unique=None, default=datetime.datetime.now)
1060
1079
1061 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1080 user = relationship('User', primaryjoin='User.user_id==UserFollowing.user_id')
1062
1081
1063 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1082 follows_user = relationship('User', primaryjoin='User.user_id==UserFollowing.follows_user_id')
1064 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1083 follows_repository = relationship('Repository', order_by='Repository.repo_name')
1065
1084
1066 @classmethod
1085 @classmethod
1067 def get_repo_followers(cls, repo_id):
1086 def get_repo_followers(cls, repo_id):
1068 return cls.query().filter(cls.follows_repo_id == repo_id)
1087 return cls.query().filter(cls.follows_repo_id == repo_id)
1069
1088
1070
1089
1071 class CacheInvalidation(Base, BaseModel):
1090 class CacheInvalidation(Base, BaseModel):
1072 __tablename__ = 'cache_invalidation'
1091 __tablename__ = 'cache_invalidation'
1073 __table_args__ = (
1092 __table_args__ = (
1074 UniqueConstraint('cache_key'),
1093 UniqueConstraint('cache_key'),
1075 {'extend_existing': True, 'mysql_engine':'InnoDB',
1094 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1076 'mysql_charset': 'utf8'},
1095 'mysql_charset': 'utf8'},
1077 )
1096 )
1078 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1097 cache_id = Column("cache_id", Integer(), nullable=False, unique=True, default=None, primary_key=True)
1079 cache_key = Column("cache_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1098 cache_key = Column("cache_key", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1080 cache_args = Column("cache_args", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1099 cache_args = Column("cache_args", String(length=255, convert_unicode=False, assert_unicode=None), nullable=True, unique=None, default=None)
1081 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1100 cache_active = Column("cache_active", Boolean(), nullable=True, unique=None, default=False)
1082
1101
1083 def __init__(self, cache_key, cache_args=''):
1102 def __init__(self, cache_key, cache_args=''):
1084 self.cache_key = cache_key
1103 self.cache_key = cache_key
1085 self.cache_args = cache_args
1104 self.cache_args = cache_args
1086 self.cache_active = False
1105 self.cache_active = False
1087
1106
1088 def __unicode__(self):
1107 def __unicode__(self):
1089 return u"<%s('%s:%s')>" % (self.__class__.__name__,
1108 return u"<%s('%s:%s')>" % (self.__class__.__name__,
1090 self.cache_id, self.cache_key)
1109 self.cache_id, self.cache_key)
1091 @classmethod
1110 @classmethod
1092 def clear_cache(cls):
1111 def clear_cache(cls):
1093 cls.query().delete()
1112 cls.query().delete()
1094
1113
1095 @classmethod
1114 @classmethod
1096 def _get_key(cls, key):
1115 def _get_key(cls, key):
1097 """
1116 """
1098 Wrapper for generating a key, together with a prefix
1117 Wrapper for generating a key, together with a prefix
1099
1118
1100 :param key:
1119 :param key:
1101 """
1120 """
1102 import rhodecode
1121 import rhodecode
1103 prefix = ''
1122 prefix = ''
1104 iid = rhodecode.CONFIG.get('instance_id')
1123 iid = rhodecode.CONFIG.get('instance_id')
1105 if iid:
1124 if iid:
1106 prefix = iid
1125 prefix = iid
1107 return "%s%s" % (prefix, key), prefix, key.rstrip('_README')
1126 return "%s%s" % (prefix, key), prefix, key.rstrip('_README')
1108
1127
1109 @classmethod
1128 @classmethod
1110 def get_by_key(cls, key):
1129 def get_by_key(cls, key):
1111 return cls.query().filter(cls.cache_key == key).scalar()
1130 return cls.query().filter(cls.cache_key == key).scalar()
1112
1131
1113 @classmethod
1132 @classmethod
1114 def _get_or_create_key(cls, key, prefix, org_key):
1133 def _get_or_create_key(cls, key, prefix, org_key):
1115 inv_obj = Session.query(cls).filter(cls.cache_key == key).scalar()
1134 inv_obj = Session.query(cls).filter(cls.cache_key == key).scalar()
1116 if not inv_obj:
1135 if not inv_obj:
1117 try:
1136 try:
1118 inv_obj = CacheInvalidation(key, org_key)
1137 inv_obj = CacheInvalidation(key, org_key)
1119 Session.add(inv_obj)
1138 Session.add(inv_obj)
1120 Session.commit()
1139 Session.commit()
1121 except Exception:
1140 except Exception:
1122 log.error(traceback.format_exc())
1141 log.error(traceback.format_exc())
1123 Session.rollback()
1142 Session.rollback()
1124 return inv_obj
1143 return inv_obj
1125
1144
1126 @classmethod
1145 @classmethod
1127 def invalidate(cls, key):
1146 def invalidate(cls, key):
1128 """
1147 """
1129 Returns Invalidation object if this given key should be invalidated
1148 Returns Invalidation object if this given key should be invalidated
1130 None otherwise. `cache_active = False` means that this cache
1149 None otherwise. `cache_active = False` means that this cache
1131 state is not valid and needs to be invalidated
1150 state is not valid and needs to be invalidated
1132
1151
1133 :param key:
1152 :param key:
1134 """
1153 """
1135
1154
1136 key, _prefix, _org_key = cls._get_key(key)
1155 key, _prefix, _org_key = cls._get_key(key)
1137 inv = cls._get_or_create_key(key, _prefix, _org_key)
1156 inv = cls._get_or_create_key(key, _prefix, _org_key)
1138
1157
1139 if inv and inv.cache_active is False:
1158 if inv and inv.cache_active is False:
1140 return inv
1159 return inv
1141
1160
1142 @classmethod
1161 @classmethod
1143 def set_invalidate(cls, key):
1162 def set_invalidate(cls, key):
1144 """
1163 """
1145 Mark this Cache key for invalidation
1164 Mark this Cache key for invalidation
1146
1165
1147 :param key:
1166 :param key:
1148 """
1167 """
1149
1168
1150 key, _prefix, _org_key = cls._get_key(key)
1169 key, _prefix, _org_key = cls._get_key(key)
1151 inv_objs = Session.query(cls).filter(cls.cache_args == _org_key).all()
1170 inv_objs = Session.query(cls).filter(cls.cache_args == _org_key).all()
1152 log.debug('marking %s key[s] %s for invalidation' % (len(inv_objs),
1171 log.debug('marking %s key[s] %s for invalidation' % (len(inv_objs),
1153 _org_key))
1172 _org_key))
1154 try:
1173 try:
1155 for inv_obj in inv_objs:
1174 for inv_obj in inv_objs:
1156 if inv_obj:
1175 if inv_obj:
1157 inv_obj.cache_active = False
1176 inv_obj.cache_active = False
1158
1177
1159 Session.add(inv_obj)
1178 Session.add(inv_obj)
1160 Session.commit()
1179 Session.commit()
1161 except Exception:
1180 except Exception:
1162 log.error(traceback.format_exc())
1181 log.error(traceback.format_exc())
1163 Session.rollback()
1182 Session.rollback()
1164
1183
1165 @classmethod
1184 @classmethod
1166 def set_valid(cls, key):
1185 def set_valid(cls, key):
1167 """
1186 """
1168 Mark this cache key as active and currently cached
1187 Mark this cache key as active and currently cached
1169
1188
1170 :param key:
1189 :param key:
1171 """
1190 """
1172 inv_obj = cls.get_by_key(key)
1191 inv_obj = cls.get_by_key(key)
1173 inv_obj.cache_active = True
1192 inv_obj.cache_active = True
1174 Session.add(inv_obj)
1193 Session.add(inv_obj)
1175 Session.commit()
1194 Session.commit()
1176
1195
1177
1196
1178 class ChangesetComment(Base, BaseModel):
1197 class ChangesetComment(Base, BaseModel):
1179 __tablename__ = 'changeset_comments'
1198 __tablename__ = 'changeset_comments'
1180 __table_args__ = (
1199 __table_args__ = (
1181 {'extend_existing': True, 'mysql_engine':'InnoDB',
1200 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1182 'mysql_charset': 'utf8'},
1201 'mysql_charset': 'utf8'},
1183 )
1202 )
1184 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1203 comment_id = Column('comment_id', Integer(), nullable=False, primary_key=True)
1185 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1204 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1186 revision = Column('revision', String(40), nullable=False)
1205 revision = Column('revision', String(40), nullable=False)
1187 line_no = Column('line_no', Unicode(10), nullable=True)
1206 line_no = Column('line_no', Unicode(10), nullable=True)
1188 f_path = Column('f_path', Unicode(1000), nullable=True)
1207 f_path = Column('f_path', Unicode(1000), nullable=True)
1189 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1208 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False)
1190 text = Column('text', Unicode(25000), nullable=False)
1209 text = Column('text', Unicode(25000), nullable=False)
1191 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1210 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1192
1211
1193 author = relationship('User', lazy='joined')
1212 author = relationship('User', lazy='joined')
1194 repo = relationship('Repository')
1213 repo = relationship('Repository')
1195
1214
1196 @classmethod
1215 @classmethod
1197 def get_users(cls, revision):
1216 def get_users(cls, revision):
1198 """
1217 """
1199 Returns user associated with this changesetComment. ie those
1218 Returns user associated with this changesetComment. ie those
1200 who actually commented
1219 who actually commented
1201
1220
1202 :param cls:
1221 :param cls:
1203 :param revision:
1222 :param revision:
1204 """
1223 """
1205 return Session.query(User)\
1224 return Session.query(User)\
1206 .filter(cls.revision == revision)\
1225 .filter(cls.revision == revision)\
1207 .join(ChangesetComment.author).all()
1226 .join(ChangesetComment.author).all()
1208
1227
1209
1228
1229 class ChangesetStatus(Base, BaseModel):
1230 __tablename__ = 'changeset_statuses'
1231 __table_args__ = (
1232 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1233 'mysql_charset': 'utf8'}
1234 )
1235
1236 STATUSES = [
1237 ('not_reviewed', _("Not Reviewed")), # (no icon) and default
1238 ('approved', _("Approved")),
1239 ('rejected', _("Rejected")),
1240 ('under_review', _("Under Review")),
1241 ]
1242
1243 changeset_status_id = Column('changeset_status_id', Integer(), nullable=False, primary_key=True)
1244 repo_id = Column('repo_id', Integer(), ForeignKey('repositories.repo_id'), nullable=False)
1245 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1246 revision = Column('revision', String(40), nullable=False)
1247 status = Column('status', String(128), nullable=False, default=STATUSES[0][0])
1248 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1249
1250 author = relationship('User', lazy='joined')
1251 repo = relationship('Repository')
1252
1253
1254 class ChangesetStatusHistory(Base, BaseModel):
1255 __tablename__ = 'changeset_statuses_history'
1256 __table_args__ = (
1257 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1258 'mysql_charset': 'utf8'}
1259 )
1260 #TODO: check if sqla has a nice history table implementation
1261 changeset_status_id = Column('changeset_status_id', Integer(), ForeignKey('changeset_statuses.changeset_status_id'), nullable=False, primary_key=True)
1262 user_id = Column("user_id", Integer(), ForeignKey('users.user_id'), nullable=False, unique=None)
1263 status = Column('status', String(128), nullable=False)
1264 modified_at = Column('modified_at', DateTime(), nullable=False, default=datetime.datetime.now)
1265
1266
1210 class Notification(Base, BaseModel):
1267 class Notification(Base, BaseModel):
1211 __tablename__ = 'notifications'
1268 __tablename__ = 'notifications'
1212 __table_args__ = (
1269 __table_args__ = (
1213 {'extend_existing': True, 'mysql_engine':'InnoDB',
1270 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1214 'mysql_charset': 'utf8'},
1271 'mysql_charset': 'utf8'},
1215 )
1272 )
1216
1273
1217 TYPE_CHANGESET_COMMENT = u'cs_comment'
1274 TYPE_CHANGESET_COMMENT = u'cs_comment'
1218 TYPE_MESSAGE = u'message'
1275 TYPE_MESSAGE = u'message'
1219 TYPE_MENTION = u'mention'
1276 TYPE_MENTION = u'mention'
1220 TYPE_REGISTRATION = u'registration'
1277 TYPE_REGISTRATION = u'registration'
1221
1278
1222 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1279 notification_id = Column('notification_id', Integer(), nullable=False, primary_key=True)
1223 subject = Column('subject', Unicode(512), nullable=True)
1280 subject = Column('subject', Unicode(512), nullable=True)
1224 body = Column('body', Unicode(50000), nullable=True)
1281 body = Column('body', Unicode(50000), nullable=True)
1225 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1282 created_by = Column("created_by", Integer(), ForeignKey('users.user_id'), nullable=True)
1226 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1283 created_on = Column('created_on', DateTime(timezone=False), nullable=False, default=datetime.datetime.now)
1227 type_ = Column('type', Unicode(256))
1284 type_ = Column('type', Unicode(256))
1228
1285
1229 created_by_user = relationship('User')
1286 created_by_user = relationship('User')
1230 notifications_to_users = relationship('UserNotification', lazy='joined',
1287 notifications_to_users = relationship('UserNotification', lazy='joined',
1231 cascade="all, delete, delete-orphan")
1288 cascade="all, delete, delete-orphan")
1232
1289
1233 @property
1290 @property
1234 def recipients(self):
1291 def recipients(self):
1235 return [x.user for x in UserNotification.query()\
1292 return [x.user for x in UserNotification.query()\
1236 .filter(UserNotification.notification == self).all()]
1293 .filter(UserNotification.notification == self).all()]
1237
1294
1238 @classmethod
1295 @classmethod
1239 def create(cls, created_by, subject, body, recipients, type_=None):
1296 def create(cls, created_by, subject, body, recipients, type_=None):
1240 if type_ is None:
1297 if type_ is None:
1241 type_ = Notification.TYPE_MESSAGE
1298 type_ = Notification.TYPE_MESSAGE
1242
1299
1243 notification = cls()
1300 notification = cls()
1244 notification.created_by_user = created_by
1301 notification.created_by_user = created_by
1245 notification.subject = subject
1302 notification.subject = subject
1246 notification.body = body
1303 notification.body = body
1247 notification.type_ = type_
1304 notification.type_ = type_
1248 notification.created_on = datetime.datetime.now()
1305 notification.created_on = datetime.datetime.now()
1249
1306
1250 for u in recipients:
1307 for u in recipients:
1251 assoc = UserNotification()
1308 assoc = UserNotification()
1252 assoc.notification = notification
1309 assoc.notification = notification
1253 u.notifications.append(assoc)
1310 u.notifications.append(assoc)
1254 Session.add(notification)
1311 Session.add(notification)
1255 return notification
1312 return notification
1256
1313
1257 @property
1314 @property
1258 def description(self):
1315 def description(self):
1259 from rhodecode.model.notification import NotificationModel
1316 from rhodecode.model.notification import NotificationModel
1260 return NotificationModel().make_description(self)
1317 return NotificationModel().make_description(self)
1261
1318
1262
1319
1263 class UserNotification(Base, BaseModel):
1320 class UserNotification(Base, BaseModel):
1264 __tablename__ = 'user_to_notification'
1321 __tablename__ = 'user_to_notification'
1265 __table_args__ = (
1322 __table_args__ = (
1266 UniqueConstraint('user_id', 'notification_id'),
1323 UniqueConstraint('user_id', 'notification_id'),
1267 {'extend_existing': True, 'mysql_engine':'InnoDB',
1324 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1268 'mysql_charset': 'utf8'}
1325 'mysql_charset': 'utf8'}
1269 )
1326 )
1270 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1327 user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), primary_key=True)
1271 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1328 notification_id = Column("notification_id", Integer(), ForeignKey('notifications.notification_id'), primary_key=True)
1272 read = Column('read', Boolean, default=False)
1329 read = Column('read', Boolean, default=False)
1273 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1330 sent_on = Column('sent_on', DateTime(timezone=False), nullable=True, unique=None)
1274
1331
1275 user = relationship('User', lazy="joined")
1332 user = relationship('User', lazy="joined")
1276 notification = relationship('Notification', lazy="joined",
1333 notification = relationship('Notification', lazy="joined",
1277 order_by=lambda: Notification.created_on.desc(),)
1334 order_by=lambda: Notification.created_on.desc(),)
1278
1335
1279 def mark_as_read(self):
1336 def mark_as_read(self):
1280 self.read = True
1337 self.read = True
1281 Session.add(self)
1338 Session.add(self)
1282
1339
1283
1340
1284 class DbMigrateVersion(Base, BaseModel):
1341 class DbMigrateVersion(Base, BaseModel):
1285 __tablename__ = 'db_migrate_version'
1342 __tablename__ = 'db_migrate_version'
1286 __table_args__ = (
1343 __table_args__ = (
1287 {'extend_existing': True, 'mysql_engine':'InnoDB',
1344 {'extend_existing': True, 'mysql_engine': 'InnoDB',
1288 'mysql_charset': 'utf8'},
1345 'mysql_charset': 'utf8'},
1289 )
1346 )
1290 repository_id = Column('repository_id', String(250), primary_key=True)
1347 repository_id = Column('repository_id', String(250), primary_key=True)
1291 repository_path = Column('repository_path', Text)
1348 repository_path = Column('repository_path', Text)
1292 version = Column('version', Integer)
1349 version = Column('version', Integer)
@@ -1,230 +1,231 b''
1 ## -*- coding: utf-8 -*-
1 ## -*- coding: utf-8 -*-
2
2
3 <%inherit file="/base/base.html"/>
3 <%inherit file="/base/base.html"/>
4
4
5 <%def name="title()">
5 <%def name="title()">
6 ${c.repo_name} ${_('Changelog')} - ${c.rhodecode_name}
6 ${c.repo_name} ${_('Changelog')} - ${c.rhodecode_name}
7 </%def>
7 </%def>
8
8
9 <%def name="breadcrumbs_links()">
9 <%def name="breadcrumbs_links()">
10 ${h.link_to(u'Home',h.url('/'))}
10 ${h.link_to(u'Home',h.url('/'))}
11 &raquo;
11 &raquo;
12 ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
12 ${h.link_to(c.repo_name,h.url('summary_home',repo_name=c.repo_name))}
13 &raquo;
13 &raquo;
14 ${_('Changelog')} - ${_('showing ')} ${c.size if c.size <= c.total_cs else c.total_cs} ${_('out of')} ${c.total_cs} ${_('revisions')}
14 ${_('Changelog')} - ${_('showing ')} ${c.size if c.size <= c.total_cs else c.total_cs} ${_('out of')} ${c.total_cs} ${_('revisions')}
15 </%def>
15 </%def>
16
16
17 <%def name="page_nav()">
17 <%def name="page_nav()">
18 ${self.menu('changelog')}
18 ${self.menu('changelog')}
19 </%def>
19 </%def>
20
20
21 <%def name="main()">
21 <%def name="main()">
22 <div class="box">
22 <div class="box">
23 <!-- box / title -->
23 <!-- box / title -->
24 <div class="title">
24 <div class="title">
25 ${self.breadcrumbs()}
25 ${self.breadcrumbs()}
26 </div>
26 </div>
27 <div class="table">
27 <div class="table">
28 % if c.pagination:
28 % if c.pagination:
29 <div id="graph">
29 <div id="graph">
30 <div id="graph_nodes">
30 <div id="graph_nodes">
31 <canvas id="graph_canvas"></canvas>
31 <canvas id="graph_canvas"></canvas>
32 </div>
32 </div>
33 <div id="graph_content">
33 <div id="graph_content">
34 <div class="container_header">
34 <div class="container_header">
35 ${h.form(h.url.current(),method='get')}
35 ${h.form(h.url.current(),method='get')}
36 <div class="info_box" style="float:left">
36 <div class="info_box" style="float:left">
37 ${h.submit('set',_('Show'),class_="ui-btn")}
37 ${h.submit('set',_('Show'),class_="ui-btn")}
38 ${h.text('size',size=1,value=c.size)}
38 ${h.text('size',size=1,value=c.size)}
39 ${_('revisions')}
39 ${_('revisions')}
40 </div>
40 </div>
41 ${h.end_form()}
41 ${h.end_form()}
42 <div id="rev_range_container" style="display:none"></div>
42 <div id="rev_range_container" style="display:none"></div>
43 <div style="float:right">${h.select('branch_filter',c.branch_name,c.branch_filters)}</div>
43 <div style="float:right">${h.select('branch_filter',c.branch_name,c.branch_filters)}</div>
44 </div>
44 </div>
45
45
46 %for cnt,cs in enumerate(c.pagination):
46 %for cnt,cs in enumerate(c.pagination):
47 <div id="chg_${cnt+1}" class="container ${'tablerow%s' % (cnt%2)}">
47 <div id="chg_${cnt+1}" class="container ${'tablerow%s' % (cnt%2)}">
48 <div class="left">
48 <div class="left">
49 <div>
49 <div>
50 ${h.checkbox(cs.short_id,class_="changeset_range")}
50 ${h.checkbox(cs.short_id,class_="changeset_range")}
51 <span class="tooltip" title="${h.age(cs.date)}"><a href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id)}"><span class="changeset_id">${cs.revision}:<span class="changeset_hash">${h.short_id(cs.raw_id)}</span></span></a></span>
51 <span class="tooltip" title="${h.age(cs.date)}"><a href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id)}"><span class="changeset_id">${cs.revision}:<span class="changeset_hash">${h.short_id(cs.raw_id)}</span></span></a></span>
52 </div>
52 </div>
53 <div class="author">
53 <div class="author">
54 <div class="gravatar">
54 <div class="gravatar">
55 <img alt="gravatar" src="${h.gravatar_url(h.email(cs.author),16)}"/>
55 <img alt="gravatar" src="${h.gravatar_url(h.email(cs.author),16)}"/>
56 </div>
56 </div>
57 <div title="${cs.author}" class="user">${h.person(cs.author)}</div>
57 <div title="${cs.author}" class="user">${h.person(cs.author)}</div>
58 </div>
58 </div>
59 <div class="date">${cs.date}</div>
59 <div class="date">${cs.date}</div>
60 </div>
60 </div>
61 <div class="mid">
61 <div class="mid">
62 <div class="message">${h.urlify_commit(h.wrap_paragraphs(cs.message),c.repo_name,h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}</div>
62 <div class="message">${h.urlify_commit(h.wrap_paragraphs(cs.message),c.repo_name,h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id))}</div>
63 <div class="expand"><span class="expandtext">&darr; ${_('show more')} &darr;</span></div>
63 <div class="expand"><span class="expandtext">&darr; ${_('show more')} &darr;</span></div>
64 </div>
64 </div>
65 <div class="right">
65 <div class="right">
66 <div id="${cs.raw_id}_changes_info" class="changes">
66 <div id="${cs.raw_id}_changes_info" class="changes">
67 <div id="${cs.raw_id}" style="float:right;" class="changed_total tooltip" title="${_('Affected number of files, click to show more details')}">${len(cs.affected_files)}</div>
67 <div id="${cs.raw_id}" style="float:right;" class="changed_total tooltip" title="${_('Affected number of files, click to show more details')}">${len(cs.affected_files)}</div>
68 <div class="changeset-status-container">${c.statuses.get(cs.raw_id)}</div>
68 <div class="comments-container">
69 <div class="comments-container">
69 %if len(c.comments.get(cs.raw_id,[])) > 0:
70 %if len(c.comments.get(cs.raw_id,[])) > 0:
70 <div class="comments-cnt" title="${('comments')}">
71 <div class="comments-cnt" title="${('comments')}">
71 <a href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id,anchor='comment-%s' % c.comments[cs.raw_id][0].comment_id)}">
72 <a href="${h.url('changeset_home',repo_name=c.repo_name,revision=cs.raw_id,anchor='comment-%s' % c.comments[cs.raw_id][0].comment_id)}">
72 <div class="comments-cnt">${len(c.comments[cs.raw_id])}</div>
73 <div class="comments-cnt">${len(c.comments[cs.raw_id])}</div>
73 <img src="${h.url('/images/icons/comments.png')}">
74 <img src="${h.url('/images/icons/comments.png')}">
74 </a>
75 </a>
75 </div>
76 </div>
76 %endif
77 %endif
77 </div>
78 </div>
78 </div>
79 </div>
79 %if cs.parents:
80 %if cs.parents:
80 %for p_cs in reversed(cs.parents):
81 %for p_cs in reversed(cs.parents):
81 <div class="parent">${_('Parent')}
82 <div class="parent">${_('Parent')}
82 <span class="changeset_id">${p_cs.revision}:<span class="changeset_hash">${h.link_to(h.short_id(p_cs.raw_id),
83 <span class="changeset_id">${p_cs.revision}:<span class="changeset_hash">${h.link_to(h.short_id(p_cs.raw_id),
83 h.url('changeset_home',repo_name=c.repo_name,revision=p_cs.raw_id),title=p_cs.message)}</span></span>
84 h.url('changeset_home',repo_name=c.repo_name,revision=p_cs.raw_id),title=p_cs.message)}</span></span>
84 </div>
85 </div>
85 %endfor
86 %endfor
86 %else:
87 %else:
87 <div class="parent">${_('No parents')}</div>
88 <div class="parent">${_('No parents')}</div>
88 %endif
89 %endif
89
90
90 <span class="logtags">
91 <span class="logtags">
91 %if len(cs.parents)>1:
92 %if len(cs.parents)>1:
92 <span class="merge">${_('merge')}</span>
93 <span class="merge">${_('merge')}</span>
93 %endif
94 %endif
94 %if cs.branch:
95 %if cs.branch:
95 <span class="branchtag" title="${'%s %s' % (_('branch'),cs.branch)}">
96 <span class="branchtag" title="${'%s %s' % (_('branch'),cs.branch)}">
96 ${h.link_to(h.shorter(cs.branch),h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}</span>
97 ${h.link_to(h.shorter(cs.branch),h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}</span>
97 %endif
98 %endif
98 %for tag in cs.tags:
99 %for tag in cs.tags:
99 <span class="tagtag" title="${'%s %s' % (_('tag'),tag)}">
100 <span class="tagtag" title="${'%s %s' % (_('tag'),tag)}">
100 ${h.link_to(h.shorter(tag),h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}</span>
101 ${h.link_to(h.shorter(tag),h.url('files_home',repo_name=c.repo_name,revision=cs.raw_id))}</span>
101 %endfor
102 %endfor
102 </span>
103 </span>
103 </div>
104 </div>
104 </div>
105 </div>
105
106
106 %endfor
107 %endfor
107 <div class="pagination-wh pagination-left">
108 <div class="pagination-wh pagination-left">
108 ${c.pagination.pager('$link_previous ~2~ $link_next')}
109 ${c.pagination.pager('$link_previous ~2~ $link_next')}
109 </div>
110 </div>
110 </div>
111 </div>
111 </div>
112 </div>
112
113
113 <script type="text/javascript" src="${h.url('/js/graph.js')}"></script>
114 <script type="text/javascript" src="${h.url('/js/graph.js')}"></script>
114 <script type="text/javascript">
115 <script type="text/javascript">
115 YAHOO.util.Event.onDOMReady(function(){
116 YAHOO.util.Event.onDOMReady(function(){
116
117
117 //Monitor range checkboxes and build a link to changesets
118 //Monitor range checkboxes and build a link to changesets
118 //ranges
119 //ranges
119 var checkboxes = YUD.getElementsByClassName('changeset_range');
120 var checkboxes = YUD.getElementsByClassName('changeset_range');
120 var url_tmpl = "${h.url('changeset_home',repo_name=c.repo_name,revision='__REVRANGE__')}";
121 var url_tmpl = "${h.url('changeset_home',repo_name=c.repo_name,revision='__REVRANGE__')}";
121 YUE.on(checkboxes,'click',function(e){
122 YUE.on(checkboxes,'click',function(e){
122 var checked_checkboxes = [];
123 var checked_checkboxes = [];
123 for (pos in checkboxes){
124 for (pos in checkboxes){
124 if(checkboxes[pos].checked){
125 if(checkboxes[pos].checked){
125 checked_checkboxes.push(checkboxes[pos]);
126 checked_checkboxes.push(checkboxes[pos]);
126 }
127 }
127 }
128 }
128 if(checked_checkboxes.length>1){
129 if(checked_checkboxes.length>1){
129 var rev_end = checked_checkboxes[0].name;
130 var rev_end = checked_checkboxes[0].name;
130 var rev_start = checked_checkboxes[checked_checkboxes.length-1].name;
131 var rev_start = checked_checkboxes[checked_checkboxes.length-1].name;
131
132
132 var url = url_tmpl.replace('__REVRANGE__',
133 var url = url_tmpl.replace('__REVRANGE__',
133 rev_start+'...'+rev_end);
134 rev_start+'...'+rev_end);
134
135
135 var link = "<a href="+url+">${_('Show selected changes __S -> __E')}</a>"
136 var link = "<a href="+url+">${_('Show selected changes __S -> __E')}</a>"
136 link = link.replace('__S',rev_start);
137 link = link.replace('__S',rev_start);
137 link = link.replace('__E',rev_end);
138 link = link.replace('__E',rev_end);
138 YUD.get('rev_range_container').innerHTML = link;
139 YUD.get('rev_range_container').innerHTML = link;
139 YUD.setStyle('rev_range_container','display','');
140 YUD.setStyle('rev_range_container','display','');
140 }
141 }
141 else{
142 else{
142 YUD.setStyle('rev_range_container','display','none');
143 YUD.setStyle('rev_range_container','display','none');
143
144
144 }
145 }
145 });
146 });
146
147
147 var msgs = YUQ('.message');
148 var msgs = YUQ('.message');
148 // get first element height
149 // get first element height
149 var el = YUQ('#graph_content .container')[0];
150 var el = YUQ('#graph_content .container')[0];
150 var row_h = el.clientHeight;
151 var row_h = el.clientHeight;
151 for(var i=0;i<msgs.length;i++){
152 for(var i=0;i<msgs.length;i++){
152 var m = msgs[i];
153 var m = msgs[i];
153
154
154 var h = m.clientHeight;
155 var h = m.clientHeight;
155 var pad = YUD.getStyle(m,'padding');
156 var pad = YUD.getStyle(m,'padding');
156 if(h > row_h){
157 if(h > row_h){
157 var offset = row_h - (h+12);
158 var offset = row_h - (h+12);
158 YUD.setStyle(m.nextElementSibling,'display','block');
159 YUD.setStyle(m.nextElementSibling,'display','block');
159 YUD.setStyle(m.nextElementSibling,'margin-top',offset+'px');
160 YUD.setStyle(m.nextElementSibling,'margin-top',offset+'px');
160 };
161 };
161 }
162 }
162 YUE.on(YUQ('.expand'),'click',function(e){
163 YUE.on(YUQ('.expand'),'click',function(e){
163 var elem = e.currentTarget.parentNode.parentNode;
164 var elem = e.currentTarget.parentNode.parentNode;
164 YUD.setStyle(e.currentTarget,'display','none');
165 YUD.setStyle(e.currentTarget,'display','none');
165 YUD.setStyle(elem,'height','auto');
166 YUD.setStyle(elem,'height','auto');
166
167
167 //redraw the graph, max_w and jsdata are global vars
168 //redraw the graph, max_w and jsdata are global vars
168 set_canvas(max_w);
169 set_canvas(max_w);
169
170
170 var r = new BranchRenderer();
171 var r = new BranchRenderer();
171 r.render(jsdata,max_w);
172 r.render(jsdata,max_w);
172
173
173 })
174 })
174
175
175 // Fetch changeset details
176 // Fetch changeset details
176 YUE.on(YUD.getElementsByClassName('changed_total'),'click',function(e){
177 YUE.on(YUD.getElementsByClassName('changed_total'),'click',function(e){
177 var id = e.currentTarget.id
178 var id = e.currentTarget.id
178 var url = "${h.url('changelog_details',repo_name=c.repo_name,cs='__CS__')}"
179 var url = "${h.url('changelog_details',repo_name=c.repo_name,cs='__CS__')}"
179 var url = url.replace('__CS__',id);
180 var url = url.replace('__CS__',id);
180 ypjax(url,id+'_changes_info',function(){tooltip_activate()});
181 ypjax(url,id+'_changes_info',function(){tooltip_activate()});
181 });
182 });
182
183
183 // change branch filter
184 // change branch filter
184 YUE.on(YUD.get('branch_filter'),'change',function(e){
185 YUE.on(YUD.get('branch_filter'),'change',function(e){
185 var selected_branch = e.currentTarget.options[e.currentTarget.selectedIndex].value;
186 var selected_branch = e.currentTarget.options[e.currentTarget.selectedIndex].value;
186 var url_main = "${h.url('changelog_home',repo_name=c.repo_name)}";
187 var url_main = "${h.url('changelog_home',repo_name=c.repo_name)}";
187 var url = "${h.url('changelog_home',repo_name=c.repo_name,branch='__BRANCH__')}";
188 var url = "${h.url('changelog_home',repo_name=c.repo_name,branch='__BRANCH__')}";
188 var url = url.replace('__BRANCH__',selected_branch);
189 var url = url.replace('__BRANCH__',selected_branch);
189 if(selected_branch != ''){
190 if(selected_branch != ''){
190 window.location = url;
191 window.location = url;
191 }else{
192 }else{
192 window.location = url_main;
193 window.location = url_main;
193 }
194 }
194
195
195 });
196 });
196
197
197 function set_canvas(heads) {
198 function set_canvas(heads) {
198 var c = document.getElementById('graph_nodes');
199 var c = document.getElementById('graph_nodes');
199 var t = document.getElementById('graph_content');
200 var t = document.getElementById('graph_content');
200 canvas = document.getElementById('graph_canvas');
201 canvas = document.getElementById('graph_canvas');
201 var div_h = t.clientHeight;
202 var div_h = t.clientHeight;
202 c.style.height=div_h+'px';
203 c.style.height=div_h+'px';
203 canvas.setAttribute('height',div_h);
204 canvas.setAttribute('height',div_h);
204 c.style.height=max_w+'px';
205 c.style.height=max_w+'px';
205 canvas.setAttribute('width',max_w);
206 canvas.setAttribute('width',max_w);
206 };
207 };
207 var heads = 1;
208 var heads = 1;
208 var max_heads = 0;
209 var max_heads = 0;
209 var jsdata = ${c.jsdata|n};
210 var jsdata = ${c.jsdata|n};
210
211
211 for( var i=0;i<jsdata.length;i++){
212 for( var i=0;i<jsdata.length;i++){
212 var m = Math.max.apply(Math, jsdata[i][1]);
213 var m = Math.max.apply(Math, jsdata[i][1]);
213 if (m>max_heads){
214 if (m>max_heads){
214 max_heads = m;
215 max_heads = m;
215 }
216 }
216 }
217 }
217 var max_w = Math.max(100,max_heads*25);
218 var max_w = Math.max(100,max_heads*25);
218 set_canvas(max_w);
219 set_canvas(max_w);
219
220
220 var r = new BranchRenderer();
221 var r = new BranchRenderer();
221 r.render(jsdata,max_w);
222 r.render(jsdata,max_w);
222
223
223 });
224 });
224 </script>
225 </script>
225 %else:
226 %else:
226 ${_('There are no changes yet')}
227 ${_('There are no changes yet')}
227 %endif
228 %endif
228 </div>
229 </div>
229 </div>
230 </div>
230 </%def>
231 </%def>
@@ -1,9 +1,11 b''
1 ## small box that displays changed/added/removed details fetched by AJAX
2
1 % if len(c.cs.affected_files) <= c.affected_files_cut_off:
3 % if len(c.cs.affected_files) <= c.affected_files_cut_off:
2 <span class="removed tooltip" title="<b>${_('removed')}</b>${h.changed_tooltip(c.cs.removed)}">${len(c.cs.removed)}</span>
4 <span class="removed tooltip" title="<b>${_('removed')}</b>${h.changed_tooltip(c.cs.removed)}">${len(c.cs.removed)}</span>
3 <span class="changed tooltip" title="<b>${_('changed')}</b>${h.changed_tooltip(c.cs.changed)}">${len(c.cs.changed)}</span>
5 <span class="changed tooltip" title="<b>${_('changed')}</b>${h.changed_tooltip(c.cs.changed)}">${len(c.cs.changed)}</span>
4 <span class="added tooltip" title="<b>${_('added')}</b>${h.changed_tooltip(c.cs.added)}">${len(c.cs.added)}</span>
6 <span class="added tooltip" title="<b>${_('added')}</b>${h.changed_tooltip(c.cs.added)}">${len(c.cs.added)}</span>
5 % else:
7 % else:
6 <span class="removed tooltip" title="${_('affected %s files') % len(c.cs.affected_files)}">!</span>
8 <span class="removed tooltip" title="${_('affected %s files') % len(c.cs.affected_files)}">!</span>
7 <span class="changed tooltip" title="${_('affected %s files') % len(c.cs.affected_files)}">!</span>
9 <span class="changed tooltip" title="${_('affected %s files') % len(c.cs.affected_files)}">!</span>
8 <span class="added tooltip" title="${_('affected %s files') % len(c.cs.affected_files)}">!</span>
10 <span class="added tooltip" title="${_('affected %s files') % len(c.cs.affected_files)}">!</span>
9 % endif
11 % endif
General Comments 0
You need to be logged in to leave comments. Login now