##// END OF EJS Templates
cleanup: fixes of checking for None...
Mads Kiilerich -
r5564:737c3704 stable
parent child Browse files
Show More
@@ -1,89 +1,89 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 # This program is free software: you can redistribute it and/or modify
2 # This program is free software: you can redistribute it and/or modify
3 # it under the terms of the GNU General Public License as published by
3 # it under the terms of the GNU General Public License as published by
4 # the Free Software Foundation, either version 3 of the License, or
4 # the Free Software Foundation, either version 3 of the License, or
5 # (at your option) any later version.
5 # (at your option) any later version.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU General Public License
12 # You should have received a copy of the GNU General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 """
14 """
15 kallithea.lib.paster_commands.update_repoinfo
15 kallithea.lib.paster_commands.update_repoinfo
16 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
16 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
17
17
18 update-repoinfo paster command for Kallithea
18 update-repoinfo paster command for Kallithea
19
19
20 This file was forked by the Kallithea project in July 2014.
20 This file was forked by the Kallithea project in July 2014.
21 Original author and date, and relevant copyright and licensing information is below:
21 Original author and date, and relevant copyright and licensing information is below:
22 :created_on: Jul 14, 2012
22 :created_on: Jul 14, 2012
23 :author: marcink
23 :author: marcink
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
25 :license: GPLv3, see LICENSE.md for more details.
25 :license: GPLv3, see LICENSE.md for more details.
26 """
26 """
27
27
28
28
29 import os
29 import os
30 import sys
30 import sys
31 import logging
31 import logging
32 import string
32 import string
33
33
34 from kallithea.lib.utils import BasePasterCommand
34 from kallithea.lib.utils import BasePasterCommand
35 from kallithea.model.db import Repository
35 from kallithea.model.db import Repository
36 from kallithea.model.repo import RepoModel
36 from kallithea.model.repo import RepoModel
37 from kallithea.model.meta import Session
37 from kallithea.model.meta import Session
38
38
39 # Add location of top level folder to sys.path
39 # Add location of top level folder to sys.path
40 from os.path import dirname as dn
40 from os.path import dirname as dn
41 rc_path = dn(dn(dn(os.path.realpath(__file__))))
41 rc_path = dn(dn(dn(os.path.realpath(__file__))))
42 sys.path.append(rc_path)
42 sys.path.append(rc_path)
43
43
44 log = logging.getLogger(__name__)
44 log = logging.getLogger(__name__)
45
45
46
46
47 class Command(BasePasterCommand):
47 class Command(BasePasterCommand):
48
48
49 max_args = 1
49 max_args = 1
50 min_args = 1
50 min_args = 1
51
51
52 usage = "CONFIG_FILE"
52 usage = "CONFIG_FILE"
53 group_name = "Kallithea"
53 group_name = "Kallithea"
54 takes_config_file = -1
54 takes_config_file = -1
55 parser = BasePasterCommand.standard_parser(verbose=True)
55 parser = BasePasterCommand.standard_parser(verbose=True)
56 summary = "Updates repositories caches for last changeset"
56 summary = "Updates repositories caches for last changeset"
57
57
58 def command(self):
58 def command(self):
59 #get SqlAlchemy session
59 #get SqlAlchemy session
60 self._init_session()
60 self._init_session()
61
61
62 repo_update_list = map(string.strip,
62 repo_update_list = map(string.strip,
63 self.options.repo_update_list.split(',')) \
63 self.options.repo_update_list.split(',')) \
64 if self.options.repo_update_list else None
64 if self.options.repo_update_list else None
65
65
66 if repo_update_list:
66 if repo_update_list is not None:
67 repo_list = list(Repository.query()\
67 repo_list = list(Repository.query()\
68 .filter(Repository.repo_name.in_(repo_update_list)))
68 .filter(Repository.repo_name.in_(repo_update_list)))
69 else:
69 else:
70 repo_list = Repository.getAll()
70 repo_list = Repository.getAll()
71 RepoModel.update_repoinfo(repositories=repo_list)
71 RepoModel.update_repoinfo(repositories=repo_list)
72 Session().commit()
72 Session().commit()
73
73
74 if self.options.invalidate_cache:
74 if self.options.invalidate_cache:
75 for r in repo_list:
75 for r in repo_list:
76 r.set_invalidate()
76 r.set_invalidate()
77 print 'Updated cache for %s repositories' % (len(repo_list))
77 print 'Updated cache for %s repositories' % (len(repo_list))
78
78
79 def update_parser(self):
79 def update_parser(self):
80 self.parser.add_option('--update-only',
80 self.parser.add_option('--update-only',
81 action='store',
81 action='store',
82 dest='repo_update_list',
82 dest='repo_update_list',
83 help="Specifies a comma separated list of repositories "
83 help="Specifies a comma separated list of repositories "
84 "to update last commit info for. OPTIONAL")
84 "to update last commit info for. OPTIONAL")
85 self.parser.add_option('--invalidate-cache',
85 self.parser.add_option('--invalidate-cache',
86 action='store_true',
86 action='store_true',
87 dest='invalidate_cache',
87 dest='invalidate_cache',
88 help="Trigger cache invalidation event for repos. "
88 help="Trigger cache invalidation event for repos. "
89 "OPTIONAL")
89 "OPTIONAL")
@@ -1,548 +1,548 b''
1 import re
1 import re
2 from itertools import chain
2 from itertools import chain
3 from dulwich import objects
3 from dulwich import objects
4 from subprocess import Popen, PIPE
4 from subprocess import Popen, PIPE
5
5
6 from kallithea.lib.vcs.conf import settings
6 from kallithea.lib.vcs.conf import settings
7 from kallithea.lib.vcs.backends.base import BaseChangeset, EmptyChangeset
7 from kallithea.lib.vcs.backends.base import BaseChangeset, EmptyChangeset
8 from kallithea.lib.vcs.exceptions import (
8 from kallithea.lib.vcs.exceptions import (
9 RepositoryError, ChangesetError, NodeDoesNotExistError, VCSError,
9 RepositoryError, ChangesetError, NodeDoesNotExistError, VCSError,
10 ChangesetDoesNotExistError, ImproperArchiveTypeError
10 ChangesetDoesNotExistError, ImproperArchiveTypeError
11 )
11 )
12 from kallithea.lib.vcs.nodes import (
12 from kallithea.lib.vcs.nodes import (
13 FileNode, DirNode, NodeKind, RootNode, SubModuleNode,
13 FileNode, DirNode, NodeKind, RootNode, SubModuleNode,
14 ChangedFileNodesGenerator, AddedFileNodesGenerator, RemovedFileNodesGenerator
14 ChangedFileNodesGenerator, AddedFileNodesGenerator, RemovedFileNodesGenerator
15 )
15 )
16 from kallithea.lib.vcs.utils import (
16 from kallithea.lib.vcs.utils import (
17 safe_unicode, safe_str, safe_int, date_fromtimestamp
17 safe_unicode, safe_str, safe_int, date_fromtimestamp
18 )
18 )
19 from kallithea.lib.vcs.utils.lazy import LazyProperty
19 from kallithea.lib.vcs.utils.lazy import LazyProperty
20
20
21
21
22 class GitChangeset(BaseChangeset):
22 class GitChangeset(BaseChangeset):
23 """
23 """
24 Represents state of the repository at single revision.
24 Represents state of the repository at single revision.
25 """
25 """
26
26
27 def __init__(self, repository, revision):
27 def __init__(self, repository, revision):
28 self._stat_modes = {}
28 self._stat_modes = {}
29 self.repository = repository
29 self.repository = repository
30 revision = safe_str(revision)
30 revision = safe_str(revision)
31 try:
31 try:
32 commit = self.repository._repo[revision]
32 commit = self.repository._repo[revision]
33 if isinstance(commit, objects.Tag):
33 if isinstance(commit, objects.Tag):
34 revision = safe_str(commit.object[1])
34 revision = safe_str(commit.object[1])
35 commit = self.repository._repo.get_object(commit.object[1])
35 commit = self.repository._repo.get_object(commit.object[1])
36 except KeyError:
36 except KeyError:
37 raise RepositoryError("Cannot get object with id %s" % revision)
37 raise RepositoryError("Cannot get object with id %s" % revision)
38 self.raw_id = revision
38 self.raw_id = revision
39 self.id = self.raw_id
39 self.id = self.raw_id
40 self.short_id = self.raw_id[:12]
40 self.short_id = self.raw_id[:12]
41 self._commit = commit
41 self._commit = commit
42 self._tree_id = commit.tree
42 self._tree_id = commit.tree
43 self._committer_property = 'committer'
43 self._committer_property = 'committer'
44 self._author_property = 'author'
44 self._author_property = 'author'
45 self._date_property = 'commit_time'
45 self._date_property = 'commit_time'
46 self._date_tz_property = 'commit_timezone'
46 self._date_tz_property = 'commit_timezone'
47 self.revision = repository.revisions.index(revision)
47 self.revision = repository.revisions.index(revision)
48
48
49 self.nodes = {}
49 self.nodes = {}
50 self._paths = {}
50 self._paths = {}
51
51
52 @LazyProperty
52 @LazyProperty
53 def message(self):
53 def message(self):
54 return safe_unicode(self._commit.message)
54 return safe_unicode(self._commit.message)
55
55
56 @LazyProperty
56 @LazyProperty
57 def committer(self):
57 def committer(self):
58 return safe_unicode(getattr(self._commit, self._committer_property))
58 return safe_unicode(getattr(self._commit, self._committer_property))
59
59
60 @LazyProperty
60 @LazyProperty
61 def author(self):
61 def author(self):
62 return safe_unicode(getattr(self._commit, self._author_property))
62 return safe_unicode(getattr(self._commit, self._author_property))
63
63
64 @LazyProperty
64 @LazyProperty
65 def date(self):
65 def date(self):
66 return date_fromtimestamp(getattr(self._commit, self._date_property),
66 return date_fromtimestamp(getattr(self._commit, self._date_property),
67 getattr(self._commit, self._date_tz_property))
67 getattr(self._commit, self._date_tz_property))
68
68
69 @LazyProperty
69 @LazyProperty
70 def _timestamp(self):
70 def _timestamp(self):
71 return getattr(self._commit, self._date_property)
71 return getattr(self._commit, self._date_property)
72
72
73 @LazyProperty
73 @LazyProperty
74 def status(self):
74 def status(self):
75 """
75 """
76 Returns modified, added, removed, deleted files for current changeset
76 Returns modified, added, removed, deleted files for current changeset
77 """
77 """
78 return self.changed, self.added, self.removed
78 return self.changed, self.added, self.removed
79
79
80 @LazyProperty
80 @LazyProperty
81 def tags(self):
81 def tags(self):
82 _tags = []
82 _tags = []
83 for tname, tsha in self.repository.tags.iteritems():
83 for tname, tsha in self.repository.tags.iteritems():
84 if tsha == self.raw_id:
84 if tsha == self.raw_id:
85 _tags.append(tname)
85 _tags.append(tname)
86 return _tags
86 return _tags
87
87
88 @LazyProperty
88 @LazyProperty
89 def branch(self):
89 def branch(self):
90
90
91 heads = self.repository._heads(reverse=False)
91 heads = self.repository._heads(reverse=False)
92
92
93 ref = heads.get(self.raw_id)
93 ref = heads.get(self.raw_id)
94 if ref:
94 if ref:
95 return safe_unicode(ref)
95 return safe_unicode(ref)
96
96
97 def _fix_path(self, path):
97 def _fix_path(self, path):
98 """
98 """
99 Paths are stored without trailing slash so we need to get rid off it if
99 Paths are stored without trailing slash so we need to get rid off it if
100 needed.
100 needed.
101 """
101 """
102 if path.endswith('/'):
102 if path.endswith('/'):
103 path = path.rstrip('/')
103 path = path.rstrip('/')
104 return path
104 return path
105
105
106 def _get_id_for_path(self, path):
106 def _get_id_for_path(self, path):
107 path = safe_str(path)
107 path = safe_str(path)
108 # FIXME: Please, spare a couple of minutes and make those codes cleaner;
108 # FIXME: Please, spare a couple of minutes and make those codes cleaner;
109 if not path in self._paths:
109 if not path in self._paths:
110 path = path.strip('/')
110 path = path.strip('/')
111 # set root tree
111 # set root tree
112 tree = self.repository._repo[self._tree_id]
112 tree = self.repository._repo[self._tree_id]
113 if path == '':
113 if path == '':
114 self._paths[''] = tree.id
114 self._paths[''] = tree.id
115 return tree.id
115 return tree.id
116 splitted = path.split('/')
116 splitted = path.split('/')
117 dirs, name = splitted[:-1], splitted[-1]
117 dirs, name = splitted[:-1], splitted[-1]
118 curdir = ''
118 curdir = ''
119
119
120 # initially extract things from root dir
120 # initially extract things from root dir
121 for item, stat, id in tree.iteritems():
121 for item, stat, id in tree.iteritems():
122 if curdir:
122 if curdir:
123 name = '/'.join((curdir, item))
123 name = '/'.join((curdir, item))
124 else:
124 else:
125 name = item
125 name = item
126 self._paths[name] = id
126 self._paths[name] = id
127 self._stat_modes[name] = stat
127 self._stat_modes[name] = stat
128
128
129 for dir in dirs:
129 for dir in dirs:
130 if curdir:
130 if curdir:
131 curdir = '/'.join((curdir, dir))
131 curdir = '/'.join((curdir, dir))
132 else:
132 else:
133 curdir = dir
133 curdir = dir
134 dir_id = None
134 dir_id = None
135 for item, stat, id in tree.iteritems():
135 for item, stat, id in tree.iteritems():
136 if dir == item:
136 if dir == item:
137 dir_id = id
137 dir_id = id
138 if dir_id:
138 if dir_id:
139 # Update tree
139 # Update tree
140 tree = self.repository._repo[dir_id]
140 tree = self.repository._repo[dir_id]
141 if not isinstance(tree, objects.Tree):
141 if not isinstance(tree, objects.Tree):
142 raise ChangesetError('%s is not a directory' % curdir)
142 raise ChangesetError('%s is not a directory' % curdir)
143 else:
143 else:
144 raise ChangesetError('%s have not been found' % curdir)
144 raise ChangesetError('%s have not been found' % curdir)
145
145
146 # cache all items from the given traversed tree
146 # cache all items from the given traversed tree
147 for item, stat, id in tree.iteritems():
147 for item, stat, id in tree.iteritems():
148 if curdir:
148 if curdir:
149 name = '/'.join((curdir, item))
149 name = '/'.join((curdir, item))
150 else:
150 else:
151 name = item
151 name = item
152 self._paths[name] = id
152 self._paths[name] = id
153 self._stat_modes[name] = stat
153 self._stat_modes[name] = stat
154 if not path in self._paths:
154 if not path in self._paths:
155 raise NodeDoesNotExistError("There is no file nor directory "
155 raise NodeDoesNotExistError("There is no file nor directory "
156 "at the given path '%s' at revision %s"
156 "at the given path '%s' at revision %s"
157 % (path, safe_str(self.short_id)))
157 % (path, safe_str(self.short_id)))
158 return self._paths[path]
158 return self._paths[path]
159
159
160 def _get_kind(self, path):
160 def _get_kind(self, path):
161 obj = self.repository._repo[self._get_id_for_path(path)]
161 obj = self.repository._repo[self._get_id_for_path(path)]
162 if isinstance(obj, objects.Blob):
162 if isinstance(obj, objects.Blob):
163 return NodeKind.FILE
163 return NodeKind.FILE
164 elif isinstance(obj, objects.Tree):
164 elif isinstance(obj, objects.Tree):
165 return NodeKind.DIR
165 return NodeKind.DIR
166
166
167 def _get_filectx(self, path):
167 def _get_filectx(self, path):
168 path = self._fix_path(path)
168 path = self._fix_path(path)
169 if self._get_kind(path) != NodeKind.FILE:
169 if self._get_kind(path) != NodeKind.FILE:
170 raise ChangesetError("File does not exist for revision %s at "
170 raise ChangesetError("File does not exist for revision %s at "
171 " '%s'" % (self.raw_id, path))
171 " '%s'" % (self.raw_id, path))
172 return path
172 return path
173
173
174 def _get_file_nodes(self):
174 def _get_file_nodes(self):
175 return chain(*(t[2] for t in self.walk()))
175 return chain(*(t[2] for t in self.walk()))
176
176
177 @LazyProperty
177 @LazyProperty
178 def parents(self):
178 def parents(self):
179 """
179 """
180 Returns list of parents changesets.
180 Returns list of parents changesets.
181 """
181 """
182 return [self.repository.get_changeset(parent)
182 return [self.repository.get_changeset(parent)
183 for parent in self._commit.parents]
183 for parent in self._commit.parents]
184
184
185 @LazyProperty
185 @LazyProperty
186 def children(self):
186 def children(self):
187 """
187 """
188 Returns list of children changesets.
188 Returns list of children changesets.
189 """
189 """
190 rev_filter = settings.GIT_REV_FILTER
190 rev_filter = settings.GIT_REV_FILTER
191 so, se = self.repository.run_git_command(
191 so, se = self.repository.run_git_command(
192 ['rev-list', rev_filter, '--children']
192 ['rev-list', rev_filter, '--children']
193 )
193 )
194
194
195 children = []
195 children = []
196 pat = re.compile(r'^%s' % self.raw_id)
196 pat = re.compile(r'^%s' % self.raw_id)
197 for l in so.splitlines():
197 for l in so.splitlines():
198 if pat.match(l):
198 if pat.match(l):
199 childs = l.split(' ')[1:]
199 childs = l.split(' ')[1:]
200 children.extend(childs)
200 children.extend(childs)
201 return [self.repository.get_changeset(cs) for cs in children]
201 return [self.repository.get_changeset(cs) for cs in children]
202
202
203 def next(self, branch=None):
203 def next(self, branch=None):
204 if branch and self.branch != branch:
204 if branch and self.branch != branch:
205 raise VCSError('Branch option used on changeset not belonging '
205 raise VCSError('Branch option used on changeset not belonging '
206 'to that branch')
206 'to that branch')
207
207
208 cs = self
208 cs = self
209 while True:
209 while True:
210 try:
210 try:
211 next_ = cs.revision + 1
211 next_ = cs.revision + 1
212 next_rev = cs.repository.revisions[next_]
212 next_rev = cs.repository.revisions[next_]
213 except IndexError:
213 except IndexError:
214 raise ChangesetDoesNotExistError
214 raise ChangesetDoesNotExistError
215 cs = cs.repository.get_changeset(next_rev)
215 cs = cs.repository.get_changeset(next_rev)
216
216
217 if not branch or branch == cs.branch:
217 if not branch or branch == cs.branch:
218 return cs
218 return cs
219
219
220 def prev(self, branch=None):
220 def prev(self, branch=None):
221 if branch and self.branch != branch:
221 if branch and self.branch != branch:
222 raise VCSError('Branch option used on changeset not belonging '
222 raise VCSError('Branch option used on changeset not belonging '
223 'to that branch')
223 'to that branch')
224
224
225 cs = self
225 cs = self
226 while True:
226 while True:
227 try:
227 try:
228 prev_ = cs.revision - 1
228 prev_ = cs.revision - 1
229 if prev_ < 0:
229 if prev_ < 0:
230 raise IndexError
230 raise IndexError
231 prev_rev = cs.repository.revisions[prev_]
231 prev_rev = cs.repository.revisions[prev_]
232 except IndexError:
232 except IndexError:
233 raise ChangesetDoesNotExistError
233 raise ChangesetDoesNotExistError
234 cs = cs.repository.get_changeset(prev_rev)
234 cs = cs.repository.get_changeset(prev_rev)
235
235
236 if not branch or branch == cs.branch:
236 if not branch or branch == cs.branch:
237 return cs
237 return cs
238
238
239 def diff(self, ignore_whitespace=True, context=3):
239 def diff(self, ignore_whitespace=True, context=3):
240 rev1 = self.parents[0] if self.parents else self.repository.EMPTY_CHANGESET
240 rev1 = self.parents[0] if self.parents else self.repository.EMPTY_CHANGESET
241 rev2 = self
241 rev2 = self
242 return ''.join(self.repository.get_diff(rev1, rev2,
242 return ''.join(self.repository.get_diff(rev1, rev2,
243 ignore_whitespace=ignore_whitespace,
243 ignore_whitespace=ignore_whitespace,
244 context=context))
244 context=context))
245
245
246 def get_file_mode(self, path):
246 def get_file_mode(self, path):
247 """
247 """
248 Returns stat mode of the file at the given ``path``.
248 Returns stat mode of the file at the given ``path``.
249 """
249 """
250 # ensure path is traversed
250 # ensure path is traversed
251 path = safe_str(path)
251 path = safe_str(path)
252 self._get_id_for_path(path)
252 self._get_id_for_path(path)
253 return self._stat_modes[path]
253 return self._stat_modes[path]
254
254
255 def get_file_content(self, path):
255 def get_file_content(self, path):
256 """
256 """
257 Returns content of the file at given ``path``.
257 Returns content of the file at given ``path``.
258 """
258 """
259 id = self._get_id_for_path(path)
259 id = self._get_id_for_path(path)
260 blob = self.repository._repo[id]
260 blob = self.repository._repo[id]
261 return blob.as_pretty_string()
261 return blob.as_pretty_string()
262
262
263 def get_file_size(self, path):
263 def get_file_size(self, path):
264 """
264 """
265 Returns size of the file at given ``path``.
265 Returns size of the file at given ``path``.
266 """
266 """
267 id = self._get_id_for_path(path)
267 id = self._get_id_for_path(path)
268 blob = self.repository._repo[id]
268 blob = self.repository._repo[id]
269 return blob.raw_length()
269 return blob.raw_length()
270
270
271 def get_file_changeset(self, path):
271 def get_file_changeset(self, path):
272 """
272 """
273 Returns last commit of the file at the given ``path``.
273 Returns last commit of the file at the given ``path``.
274 """
274 """
275 return self.get_file_history(path, limit=1)[0]
275 return self.get_file_history(path, limit=1)[0]
276
276
277 def get_file_history(self, path, limit=None):
277 def get_file_history(self, path, limit=None):
278 """
278 """
279 Returns history of file as reversed list of ``Changeset`` objects for
279 Returns history of file as reversed list of ``Changeset`` objects for
280 which file at given ``path`` has been modified.
280 which file at given ``path`` has been modified.
281
281
282 TODO: This function now uses os underlying 'git' and 'grep' commands
282 TODO: This function now uses os underlying 'git' and 'grep' commands
283 which is generally not good. Should be replaced with algorithm
283 which is generally not good. Should be replaced with algorithm
284 iterating commits.
284 iterating commits.
285 """
285 """
286 self._get_filectx(path)
286 self._get_filectx(path)
287 cs_id = safe_str(self.id)
287 cs_id = safe_str(self.id)
288 f_path = safe_str(path)
288 f_path = safe_str(path)
289
289
290 if limit:
290 if limit is not None:
291 cmd = ['log', '-n', str(safe_int(limit, 0)),
291 cmd = ['log', '-n', str(safe_int(limit, 0)),
292 '--pretty=format:%H', '-s', cs_id, '--', f_path]
292 '--pretty=format:%H', '-s', cs_id, '--', f_path]
293
293
294 else:
294 else:
295 cmd = ['log',
295 cmd = ['log',
296 '--pretty=format:%H', '-s', cs_id, '--', f_path]
296 '--pretty=format:%H', '-s', cs_id, '--', f_path]
297 so, se = self.repository.run_git_command(cmd)
297 so, se = self.repository.run_git_command(cmd)
298 ids = re.findall(r'[0-9a-fA-F]{40}', so)
298 ids = re.findall(r'[0-9a-fA-F]{40}', so)
299 return [self.repository.get_changeset(sha) for sha in ids]
299 return [self.repository.get_changeset(sha) for sha in ids]
300
300
301 def get_file_history_2(self, path):
301 def get_file_history_2(self, path):
302 """
302 """
303 Returns history of file as reversed list of ``Changeset`` objects for
303 Returns history of file as reversed list of ``Changeset`` objects for
304 which file at given ``path`` has been modified.
304 which file at given ``path`` has been modified.
305
305
306 """
306 """
307 self._get_filectx(path)
307 self._get_filectx(path)
308 from dulwich.walk import Walker
308 from dulwich.walk import Walker
309 include = [self.id]
309 include = [self.id]
310 walker = Walker(self.repository._repo.object_store, include,
310 walker = Walker(self.repository._repo.object_store, include,
311 paths=[path], max_entries=1)
311 paths=[path], max_entries=1)
312 return [self.repository.get_changeset(sha)
312 return [self.repository.get_changeset(sha)
313 for sha in (x.commit.id for x in walker)]
313 for sha in (x.commit.id for x in walker)]
314
314
315 def get_file_annotate(self, path):
315 def get_file_annotate(self, path):
316 """
316 """
317 Returns a generator of four element tuples with
317 Returns a generator of four element tuples with
318 lineno, sha, changeset lazy loader and line
318 lineno, sha, changeset lazy loader and line
319
319
320 TODO: This function now uses os underlying 'git' command which is
320 TODO: This function now uses os underlying 'git' command which is
321 generally not good. Should be replaced with algorithm iterating
321 generally not good. Should be replaced with algorithm iterating
322 commits.
322 commits.
323 """
323 """
324 cmd = ['blame', '-l', '--root', '-r', self.id, '--', path]
324 cmd = ['blame', '-l', '--root', '-r', self.id, '--', path]
325 # -l ==> outputs long shas (and we need all 40 characters)
325 # -l ==> outputs long shas (and we need all 40 characters)
326 # --root ==> doesn't put '^' character for boundaries
326 # --root ==> doesn't put '^' character for boundaries
327 # -r sha ==> blames for the given revision
327 # -r sha ==> blames for the given revision
328 so, se = self.repository.run_git_command(cmd)
328 so, se = self.repository.run_git_command(cmd)
329
329
330 for i, blame_line in enumerate(so.split('\n')[:-1]):
330 for i, blame_line in enumerate(so.split('\n')[:-1]):
331 ln_no = i + 1
331 ln_no = i + 1
332 sha, line = re.split(r' ', blame_line, 1)
332 sha, line = re.split(r' ', blame_line, 1)
333 yield (ln_no, sha, lambda: self.repository.get_changeset(sha), line)
333 yield (ln_no, sha, lambda: self.repository.get_changeset(sha), line)
334
334
335 def fill_archive(self, stream=None, kind='tgz', prefix=None,
335 def fill_archive(self, stream=None, kind='tgz', prefix=None,
336 subrepos=False):
336 subrepos=False):
337 """
337 """
338 Fills up given stream.
338 Fills up given stream.
339
339
340 :param stream: file like object.
340 :param stream: file like object.
341 :param kind: one of following: ``zip``, ``tgz`` or ``tbz2``.
341 :param kind: one of following: ``zip``, ``tgz`` or ``tbz2``.
342 Default: ``tgz``.
342 Default: ``tgz``.
343 :param prefix: name of root directory in archive.
343 :param prefix: name of root directory in archive.
344 Default is repository name and changeset's raw_id joined with dash
344 Default is repository name and changeset's raw_id joined with dash
345 (``repo-tip.<KIND>``).
345 (``repo-tip.<KIND>``).
346 :param subrepos: include subrepos in this archive.
346 :param subrepos: include subrepos in this archive.
347
347
348 :raise ImproperArchiveTypeError: If given kind is wrong.
348 :raise ImproperArchiveTypeError: If given kind is wrong.
349 :raise VcsError: If given stream is None
349 :raise VcsError: If given stream is None
350
350
351 """
351 """
352 allowed_kinds = settings.ARCHIVE_SPECS.keys()
352 allowed_kinds = settings.ARCHIVE_SPECS.keys()
353 if kind not in allowed_kinds:
353 if kind not in allowed_kinds:
354 raise ImproperArchiveTypeError('Archive kind not supported use one'
354 raise ImproperArchiveTypeError('Archive kind not supported use one'
355 'of %s', allowed_kinds)
355 'of %s', allowed_kinds)
356
356
357 if prefix is None:
357 if prefix is None:
358 prefix = '%s-%s' % (self.repository.name, self.short_id)
358 prefix = '%s-%s' % (self.repository.name, self.short_id)
359 elif prefix.startswith('/'):
359 elif prefix.startswith('/'):
360 raise VCSError("Prefix cannot start with leading slash")
360 raise VCSError("Prefix cannot start with leading slash")
361 elif prefix.strip() == '':
361 elif prefix.strip() == '':
362 raise VCSError("Prefix cannot be empty")
362 raise VCSError("Prefix cannot be empty")
363
363
364 if kind == 'zip':
364 if kind == 'zip':
365 frmt = 'zip'
365 frmt = 'zip'
366 else:
366 else:
367 frmt = 'tar'
367 frmt = 'tar'
368 _git_path = settings.GIT_EXECUTABLE_PATH
368 _git_path = settings.GIT_EXECUTABLE_PATH
369 cmd = '%s archive --format=%s --prefix=%s/ %s' % (_git_path,
369 cmd = '%s archive --format=%s --prefix=%s/ %s' % (_git_path,
370 frmt, prefix, self.raw_id)
370 frmt, prefix, self.raw_id)
371 if kind == 'tgz':
371 if kind == 'tgz':
372 cmd += ' | gzip -9'
372 cmd += ' | gzip -9'
373 elif kind == 'tbz2':
373 elif kind == 'tbz2':
374 cmd += ' | bzip2 -9'
374 cmd += ' | bzip2 -9'
375
375
376 if stream is None:
376 if stream is None:
377 raise VCSError('You need to pass in a valid stream for filling'
377 raise VCSError('You need to pass in a valid stream for filling'
378 ' with archival data')
378 ' with archival data')
379 popen = Popen(cmd, stdout=PIPE, stderr=PIPE, shell=True,
379 popen = Popen(cmd, stdout=PIPE, stderr=PIPE, shell=True,
380 cwd=self.repository.path)
380 cwd=self.repository.path)
381
381
382 buffer_size = 1024 * 8
382 buffer_size = 1024 * 8
383 chunk = popen.stdout.read(buffer_size)
383 chunk = popen.stdout.read(buffer_size)
384 while chunk:
384 while chunk:
385 stream.write(chunk)
385 stream.write(chunk)
386 chunk = popen.stdout.read(buffer_size)
386 chunk = popen.stdout.read(buffer_size)
387 # Make sure all descriptors would be read
387 # Make sure all descriptors would be read
388 popen.communicate()
388 popen.communicate()
389
389
390 def get_nodes(self, path):
390 def get_nodes(self, path):
391 if self._get_kind(path) != NodeKind.DIR:
391 if self._get_kind(path) != NodeKind.DIR:
392 raise ChangesetError("Directory does not exist for revision %s at "
392 raise ChangesetError("Directory does not exist for revision %s at "
393 " '%s'" % (self.revision, path))
393 " '%s'" % (self.revision, path))
394 path = self._fix_path(path)
394 path = self._fix_path(path)
395 id = self._get_id_for_path(path)
395 id = self._get_id_for_path(path)
396 tree = self.repository._repo[id]
396 tree = self.repository._repo[id]
397 dirnodes = []
397 dirnodes = []
398 filenodes = []
398 filenodes = []
399 als = self.repository.alias
399 als = self.repository.alias
400 for name, stat, id in tree.iteritems():
400 for name, stat, id in tree.iteritems():
401 if objects.S_ISGITLINK(stat):
401 if objects.S_ISGITLINK(stat):
402 dirnodes.append(SubModuleNode(name, url=None, changeset=id,
402 dirnodes.append(SubModuleNode(name, url=None, changeset=id,
403 alias=als))
403 alias=als))
404 continue
404 continue
405
405
406 obj = self.repository._repo.get_object(id)
406 obj = self.repository._repo.get_object(id)
407 if path != '':
407 if path != '':
408 obj_path = '/'.join((path, name))
408 obj_path = '/'.join((path, name))
409 else:
409 else:
410 obj_path = name
410 obj_path = name
411 if obj_path not in self._stat_modes:
411 if obj_path not in self._stat_modes:
412 self._stat_modes[obj_path] = stat
412 self._stat_modes[obj_path] = stat
413 if isinstance(obj, objects.Tree):
413 if isinstance(obj, objects.Tree):
414 dirnodes.append(DirNode(obj_path, changeset=self))
414 dirnodes.append(DirNode(obj_path, changeset=self))
415 elif isinstance(obj, objects.Blob):
415 elif isinstance(obj, objects.Blob):
416 filenodes.append(FileNode(obj_path, changeset=self, mode=stat))
416 filenodes.append(FileNode(obj_path, changeset=self, mode=stat))
417 else:
417 else:
418 raise ChangesetError("Requested object should be Tree "
418 raise ChangesetError("Requested object should be Tree "
419 "or Blob, is %r" % type(obj))
419 "or Blob, is %r" % type(obj))
420 nodes = dirnodes + filenodes
420 nodes = dirnodes + filenodes
421 for node in nodes:
421 for node in nodes:
422 if not node.path in self.nodes:
422 if not node.path in self.nodes:
423 self.nodes[node.path] = node
423 self.nodes[node.path] = node
424 nodes.sort()
424 nodes.sort()
425 return nodes
425 return nodes
426
426
427 def get_node(self, path):
427 def get_node(self, path):
428 if isinstance(path, unicode):
428 if isinstance(path, unicode):
429 path = path.encode('utf-8')
429 path = path.encode('utf-8')
430 path = self._fix_path(path)
430 path = self._fix_path(path)
431 if not path in self.nodes:
431 if not path in self.nodes:
432 try:
432 try:
433 id_ = self._get_id_for_path(path)
433 id_ = self._get_id_for_path(path)
434 except ChangesetError:
434 except ChangesetError:
435 raise NodeDoesNotExistError("Cannot find one of parents' "
435 raise NodeDoesNotExistError("Cannot find one of parents' "
436 "directories for a given path: %s" % path)
436 "directories for a given path: %s" % path)
437
437
438 _GL = lambda m: m and objects.S_ISGITLINK(m)
438 _GL = lambda m: m and objects.S_ISGITLINK(m)
439 if _GL(self._stat_modes.get(path)):
439 if _GL(self._stat_modes.get(path)):
440 node = SubModuleNode(path, url=None, changeset=id_,
440 node = SubModuleNode(path, url=None, changeset=id_,
441 alias=self.repository.alias)
441 alias=self.repository.alias)
442 else:
442 else:
443 obj = self.repository._repo.get_object(id_)
443 obj = self.repository._repo.get_object(id_)
444
444
445 if isinstance(obj, objects.Tree):
445 if isinstance(obj, objects.Tree):
446 if path == '':
446 if path == '':
447 node = RootNode(changeset=self)
447 node = RootNode(changeset=self)
448 else:
448 else:
449 node = DirNode(path, changeset=self)
449 node = DirNode(path, changeset=self)
450 node._tree = obj
450 node._tree = obj
451 elif isinstance(obj, objects.Blob):
451 elif isinstance(obj, objects.Blob):
452 node = FileNode(path, changeset=self)
452 node = FileNode(path, changeset=self)
453 node._blob = obj
453 node._blob = obj
454 else:
454 else:
455 raise NodeDoesNotExistError("There is no file nor directory "
455 raise NodeDoesNotExistError("There is no file nor directory "
456 "at the given path '%s' at revision %s"
456 "at the given path '%s' at revision %s"
457 % (path, self.short_id))
457 % (path, self.short_id))
458 # cache node
458 # cache node
459 self.nodes[path] = node
459 self.nodes[path] = node
460 return self.nodes[path]
460 return self.nodes[path]
461
461
462 @LazyProperty
462 @LazyProperty
463 def affected_files(self):
463 def affected_files(self):
464 """
464 """
465 Gets a fast accessible file changes for given changeset
465 Gets a fast accessible file changes for given changeset
466 """
466 """
467 added, modified, deleted = self._changes_cache
467 added, modified, deleted = self._changes_cache
468 return list(added.union(modified).union(deleted))
468 return list(added.union(modified).union(deleted))
469
469
470 @LazyProperty
470 @LazyProperty
471 def _diff_name_status(self):
471 def _diff_name_status(self):
472 output = []
472 output = []
473 for parent in self.parents:
473 for parent in self.parents:
474 cmd = ['diff', '--name-status', parent.raw_id, self.raw_id,
474 cmd = ['diff', '--name-status', parent.raw_id, self.raw_id,
475 '--encoding=utf8']
475 '--encoding=utf8']
476 so, se = self.repository.run_git_command(cmd)
476 so, se = self.repository.run_git_command(cmd)
477 output.append(so.strip())
477 output.append(so.strip())
478 return '\n'.join(output)
478 return '\n'.join(output)
479
479
480 @LazyProperty
480 @LazyProperty
481 def _changes_cache(self):
481 def _changes_cache(self):
482 added = set()
482 added = set()
483 modified = set()
483 modified = set()
484 deleted = set()
484 deleted = set()
485 _r = self.repository._repo
485 _r = self.repository._repo
486
486
487 parents = self.parents
487 parents = self.parents
488 if not self.parents:
488 if not self.parents:
489 parents = [EmptyChangeset()]
489 parents = [EmptyChangeset()]
490 for parent in parents:
490 for parent in parents:
491 if isinstance(parent, EmptyChangeset):
491 if isinstance(parent, EmptyChangeset):
492 oid = None
492 oid = None
493 else:
493 else:
494 oid = _r[parent.raw_id].tree
494 oid = _r[parent.raw_id].tree
495 changes = _r.object_store.tree_changes(oid, _r[self.raw_id].tree)
495 changes = _r.object_store.tree_changes(oid, _r[self.raw_id].tree)
496 for (oldpath, newpath), (_, _), (_, _) in changes:
496 for (oldpath, newpath), (_, _), (_, _) in changes:
497 if newpath and oldpath:
497 if newpath and oldpath:
498 modified.add(newpath)
498 modified.add(newpath)
499 elif newpath and not oldpath:
499 elif newpath and not oldpath:
500 added.add(newpath)
500 added.add(newpath)
501 elif not newpath and oldpath:
501 elif not newpath and oldpath:
502 deleted.add(oldpath)
502 deleted.add(oldpath)
503 return added, modified, deleted
503 return added, modified, deleted
504
504
505 def _get_paths_for_status(self, status):
505 def _get_paths_for_status(self, status):
506 """
506 """
507 Returns sorted list of paths for given ``status``.
507 Returns sorted list of paths for given ``status``.
508
508
509 :param status: one of: *added*, *modified* or *deleted*
509 :param status: one of: *added*, *modified* or *deleted*
510 """
510 """
511 added, modified, deleted = self._changes_cache
511 added, modified, deleted = self._changes_cache
512 return sorted({
512 return sorted({
513 'added': list(added),
513 'added': list(added),
514 'modified': list(modified),
514 'modified': list(modified),
515 'deleted': list(deleted)}[status]
515 'deleted': list(deleted)}[status]
516 )
516 )
517
517
518 @LazyProperty
518 @LazyProperty
519 def added(self):
519 def added(self):
520 """
520 """
521 Returns list of added ``FileNode`` objects.
521 Returns list of added ``FileNode`` objects.
522 """
522 """
523 if not self.parents:
523 if not self.parents:
524 return list(self._get_file_nodes())
524 return list(self._get_file_nodes())
525 return AddedFileNodesGenerator([n for n in
525 return AddedFileNodesGenerator([n for n in
526 self._get_paths_for_status('added')], self)
526 self._get_paths_for_status('added')], self)
527
527
528 @LazyProperty
528 @LazyProperty
529 def changed(self):
529 def changed(self):
530 """
530 """
531 Returns list of modified ``FileNode`` objects.
531 Returns list of modified ``FileNode`` objects.
532 """
532 """
533 if not self.parents:
533 if not self.parents:
534 return []
534 return []
535 return ChangedFileNodesGenerator([n for n in
535 return ChangedFileNodesGenerator([n for n in
536 self._get_paths_for_status('modified')], self)
536 self._get_paths_for_status('modified')], self)
537
537
538 @LazyProperty
538 @LazyProperty
539 def removed(self):
539 def removed(self):
540 """
540 """
541 Returns list of removed ``FileNode`` objects.
541 Returns list of removed ``FileNode`` objects.
542 """
542 """
543 if not self.parents:
543 if not self.parents:
544 return []
544 return []
545 return RemovedFileNodesGenerator([n for n in
545 return RemovedFileNodesGenerator([n for n in
546 self._get_paths_for_status('deleted')], self)
546 self._get_paths_for_status('deleted')], self)
547
547
548 extra = {}
548 extra = {}
@@ -1,400 +1,400 b''
1 import os
1 import os
2 import posixpath
2 import posixpath
3
3
4 from kallithea.lib.vcs.conf import settings
4 from kallithea.lib.vcs.conf import settings
5 from kallithea.lib.vcs.backends.base import BaseChangeset
5 from kallithea.lib.vcs.backends.base import BaseChangeset
6 from kallithea.lib.vcs.exceptions import (
6 from kallithea.lib.vcs.exceptions import (
7 ChangesetDoesNotExistError, ChangesetError, ImproperArchiveTypeError,
7 ChangesetDoesNotExistError, ChangesetError, ImproperArchiveTypeError,
8 NodeDoesNotExistError, VCSError
8 NodeDoesNotExistError, VCSError
9 )
9 )
10 from kallithea.lib.vcs.nodes import (
10 from kallithea.lib.vcs.nodes import (
11 AddedFileNodesGenerator, ChangedFileNodesGenerator, DirNode, FileNode,
11 AddedFileNodesGenerator, ChangedFileNodesGenerator, DirNode, FileNode,
12 NodeKind, RemovedFileNodesGenerator, RootNode, SubModuleNode
12 NodeKind, RemovedFileNodesGenerator, RootNode, SubModuleNode
13 )
13 )
14 from kallithea.lib.vcs.utils import safe_str, safe_unicode, date_fromtimestamp
14 from kallithea.lib.vcs.utils import safe_str, safe_unicode, date_fromtimestamp
15 from kallithea.lib.vcs.utils.lazy import LazyProperty
15 from kallithea.lib.vcs.utils.lazy import LazyProperty
16 from kallithea.lib.vcs.utils.paths import get_dirs_for_path
16 from kallithea.lib.vcs.utils.paths import get_dirs_for_path
17 from kallithea.lib.vcs.utils.hgcompat import archival, hex
17 from kallithea.lib.vcs.utils.hgcompat import archival, hex
18
18
19 from mercurial import obsolete
19 from mercurial import obsolete
20
20
21 class MercurialChangeset(BaseChangeset):
21 class MercurialChangeset(BaseChangeset):
22 """
22 """
23 Represents state of the repository at the single revision.
23 Represents state of the repository at the single revision.
24 """
24 """
25
25
26 def __init__(self, repository, revision):
26 def __init__(self, repository, revision):
27 self.repository = repository
27 self.repository = repository
28 assert isinstance(revision, basestring), repr(revision)
28 assert isinstance(revision, basestring), repr(revision)
29 self.raw_id = revision
29 self.raw_id = revision
30 self._ctx = repository._repo[revision]
30 self._ctx = repository._repo[revision]
31 self.revision = self._ctx._rev
31 self.revision = self._ctx._rev
32 self.nodes = {}
32 self.nodes = {}
33
33
34 @LazyProperty
34 @LazyProperty
35 def tags(self):
35 def tags(self):
36 return map(safe_unicode, self._ctx.tags())
36 return map(safe_unicode, self._ctx.tags())
37
37
38 @LazyProperty
38 @LazyProperty
39 def branch(self):
39 def branch(self):
40 return safe_unicode(self._ctx.branch())
40 return safe_unicode(self._ctx.branch())
41
41
42 @LazyProperty
42 @LazyProperty
43 def closesbranch(self):
43 def closesbranch(self):
44 return self._ctx.closesbranch()
44 return self._ctx.closesbranch()
45
45
46 @LazyProperty
46 @LazyProperty
47 def obsolete(self):
47 def obsolete(self):
48 return self._ctx.obsolete()
48 return self._ctx.obsolete()
49
49
50 @LazyProperty
50 @LazyProperty
51 def successors(self):
51 def successors(self):
52 successors = obsolete.successorssets(self._ctx._repo, self._ctx.node())
52 successors = obsolete.successorssets(self._ctx._repo, self._ctx.node())
53 if successors:
53 if successors:
54 # flatten the list here handles both divergent (len > 1)
54 # flatten the list here handles both divergent (len > 1)
55 # and the usual case (len = 1)
55 # and the usual case (len = 1)
56 successors = [hex(n)[:12] for sub in successors for n in sub if n != self._ctx.node()]
56 successors = [hex(n)[:12] for sub in successors for n in sub if n != self._ctx.node()]
57
57
58 return successors
58 return successors
59
59
60 @LazyProperty
60 @LazyProperty
61 def precursors(self):
61 def precursors(self):
62 precursors = set()
62 precursors = set()
63 nm = self._ctx._repo.changelog.nodemap
63 nm = self._ctx._repo.changelog.nodemap
64 for p in self._ctx._repo.obsstore.precursors.get(self._ctx.node(), ()):
64 for p in self._ctx._repo.obsstore.precursors.get(self._ctx.node(), ()):
65 pr = nm.get(p[0])
65 pr = nm.get(p[0])
66 if pr is not None:
66 if pr is not None:
67 precursors.add(hex(p[0])[:12])
67 precursors.add(hex(p[0])[:12])
68 return precursors
68 return precursors
69
69
70 @LazyProperty
70 @LazyProperty
71 def bookmarks(self):
71 def bookmarks(self):
72 return map(safe_unicode, self._ctx.bookmarks())
72 return map(safe_unicode, self._ctx.bookmarks())
73
73
74 @LazyProperty
74 @LazyProperty
75 def message(self):
75 def message(self):
76 return safe_unicode(self._ctx.description())
76 return safe_unicode(self._ctx.description())
77
77
78 @LazyProperty
78 @LazyProperty
79 def committer(self):
79 def committer(self):
80 return safe_unicode(self.author)
80 return safe_unicode(self.author)
81
81
82 @LazyProperty
82 @LazyProperty
83 def author(self):
83 def author(self):
84 return safe_unicode(self._ctx.user())
84 return safe_unicode(self._ctx.user())
85
85
86 @LazyProperty
86 @LazyProperty
87 def date(self):
87 def date(self):
88 return date_fromtimestamp(*self._ctx.date())
88 return date_fromtimestamp(*self._ctx.date())
89
89
90 @LazyProperty
90 @LazyProperty
91 def _timestamp(self):
91 def _timestamp(self):
92 return self._ctx.date()[0]
92 return self._ctx.date()[0]
93
93
94 @LazyProperty
94 @LazyProperty
95 def status(self):
95 def status(self):
96 """
96 """
97 Returns modified, added, removed, deleted files for current changeset
97 Returns modified, added, removed, deleted files for current changeset
98 """
98 """
99 return self.repository._repo.status(self._ctx.p1().node(),
99 return self.repository._repo.status(self._ctx.p1().node(),
100 self._ctx.node())
100 self._ctx.node())
101
101
102 @LazyProperty
102 @LazyProperty
103 def _file_paths(self):
103 def _file_paths(self):
104 return list(self._ctx)
104 return list(self._ctx)
105
105
106 @LazyProperty
106 @LazyProperty
107 def _dir_paths(self):
107 def _dir_paths(self):
108 p = list(set(get_dirs_for_path(*self._file_paths)))
108 p = list(set(get_dirs_for_path(*self._file_paths)))
109 p.insert(0, '')
109 p.insert(0, '')
110 return p
110 return p
111
111
112 @LazyProperty
112 @LazyProperty
113 def _paths(self):
113 def _paths(self):
114 return self._dir_paths + self._file_paths
114 return self._dir_paths + self._file_paths
115
115
116 @LazyProperty
116 @LazyProperty
117 def id(self):
117 def id(self):
118 if self.last:
118 if self.last:
119 return u'tip'
119 return u'tip'
120 return self.short_id
120 return self.short_id
121
121
122 @LazyProperty
122 @LazyProperty
123 def short_id(self):
123 def short_id(self):
124 return self.raw_id[:12]
124 return self.raw_id[:12]
125
125
126 @LazyProperty
126 @LazyProperty
127 def parents(self):
127 def parents(self):
128 """
128 """
129 Returns list of parents changesets.
129 Returns list of parents changesets.
130 """
130 """
131 return [self.repository.get_changeset(parent.rev())
131 return [self.repository.get_changeset(parent.rev())
132 for parent in self._ctx.parents() if parent.rev() >= 0]
132 for parent in self._ctx.parents() if parent.rev() >= 0]
133
133
134 @LazyProperty
134 @LazyProperty
135 def children(self):
135 def children(self):
136 """
136 """
137 Returns list of children changesets.
137 Returns list of children changesets.
138 """
138 """
139 return [self.repository.get_changeset(child.rev())
139 return [self.repository.get_changeset(child.rev())
140 for child in self._ctx.children() if child.rev() >= 0]
140 for child in self._ctx.children() if child.rev() >= 0]
141
141
142 def next(self, branch=None):
142 def next(self, branch=None):
143 if branch and self.branch != branch:
143 if branch and self.branch != branch:
144 raise VCSError('Branch option used on changeset not belonging '
144 raise VCSError('Branch option used on changeset not belonging '
145 'to that branch')
145 'to that branch')
146
146
147 cs = self
147 cs = self
148 while True:
148 while True:
149 try:
149 try:
150 next_ = cs.revision + 1
150 next_ = cs.revision + 1
151 next_rev = cs.repository.revisions[next_]
151 next_rev = cs.repository.revisions[next_]
152 except IndexError:
152 except IndexError:
153 raise ChangesetDoesNotExistError
153 raise ChangesetDoesNotExistError
154 cs = cs.repository.get_changeset(next_rev)
154 cs = cs.repository.get_changeset(next_rev)
155
155
156 if not branch or branch == cs.branch:
156 if not branch or branch == cs.branch:
157 return cs
157 return cs
158
158
159 def prev(self, branch=None):
159 def prev(self, branch=None):
160 if branch and self.branch != branch:
160 if branch and self.branch != branch:
161 raise VCSError('Branch option used on changeset not belonging '
161 raise VCSError('Branch option used on changeset not belonging '
162 'to that branch')
162 'to that branch')
163
163
164 cs = self
164 cs = self
165 while True:
165 while True:
166 try:
166 try:
167 prev_ = cs.revision - 1
167 prev_ = cs.revision - 1
168 if prev_ < 0:
168 if prev_ < 0:
169 raise IndexError
169 raise IndexError
170 prev_rev = cs.repository.revisions[prev_]
170 prev_rev = cs.repository.revisions[prev_]
171 except IndexError:
171 except IndexError:
172 raise ChangesetDoesNotExistError
172 raise ChangesetDoesNotExistError
173 cs = cs.repository.get_changeset(prev_rev)
173 cs = cs.repository.get_changeset(prev_rev)
174
174
175 if not branch or branch == cs.branch:
175 if not branch or branch == cs.branch:
176 return cs
176 return cs
177
177
178 def diff(self, ignore_whitespace=True, context=3):
178 def diff(self, ignore_whitespace=True, context=3):
179 return ''.join(self._ctx.diff(git=True,
179 return ''.join(self._ctx.diff(git=True,
180 ignore_whitespace=ignore_whitespace,
180 ignore_whitespace=ignore_whitespace,
181 context=context))
181 context=context))
182
182
183 def _fix_path(self, path):
183 def _fix_path(self, path):
184 """
184 """
185 Paths are stored without trailing slash so we need to get rid off it if
185 Paths are stored without trailing slash so we need to get rid off it if
186 needed. Also mercurial keeps filenodes as str so we need to decode
186 needed. Also mercurial keeps filenodes as str so we need to decode
187 from unicode to str
187 from unicode to str
188 """
188 """
189 if path.endswith('/'):
189 if path.endswith('/'):
190 path = path.rstrip('/')
190 path = path.rstrip('/')
191
191
192 return safe_str(path)
192 return safe_str(path)
193
193
194 def _get_kind(self, path):
194 def _get_kind(self, path):
195 path = self._fix_path(path)
195 path = self._fix_path(path)
196 if path in self._file_paths:
196 if path in self._file_paths:
197 return NodeKind.FILE
197 return NodeKind.FILE
198 elif path in self._dir_paths:
198 elif path in self._dir_paths:
199 return NodeKind.DIR
199 return NodeKind.DIR
200 else:
200 else:
201 raise ChangesetError("Node does not exist at the given path '%s'"
201 raise ChangesetError("Node does not exist at the given path '%s'"
202 % (path))
202 % (path))
203
203
204 def _get_filectx(self, path):
204 def _get_filectx(self, path):
205 path = self._fix_path(path)
205 path = self._fix_path(path)
206 if self._get_kind(path) != NodeKind.FILE:
206 if self._get_kind(path) != NodeKind.FILE:
207 raise ChangesetError("File does not exist for revision %s at "
207 raise ChangesetError("File does not exist for revision %s at "
208 " '%s'" % (self.raw_id, path))
208 " '%s'" % (self.raw_id, path))
209 return self._ctx.filectx(path)
209 return self._ctx.filectx(path)
210
210
211 def _extract_submodules(self):
211 def _extract_submodules(self):
212 """
212 """
213 returns a dictionary with submodule information from substate file
213 returns a dictionary with submodule information from substate file
214 of hg repository
214 of hg repository
215 """
215 """
216 return self._ctx.substate
216 return self._ctx.substate
217
217
218 def get_file_mode(self, path):
218 def get_file_mode(self, path):
219 """
219 """
220 Returns stat mode of the file at the given ``path``.
220 Returns stat mode of the file at the given ``path``.
221 """
221 """
222 fctx = self._get_filectx(path)
222 fctx = self._get_filectx(path)
223 if 'x' in fctx.flags():
223 if 'x' in fctx.flags():
224 return 0100755
224 return 0100755
225 else:
225 else:
226 return 0100644
226 return 0100644
227
227
228 def get_file_content(self, path):
228 def get_file_content(self, path):
229 """
229 """
230 Returns content of the file at given ``path``.
230 Returns content of the file at given ``path``.
231 """
231 """
232 fctx = self._get_filectx(path)
232 fctx = self._get_filectx(path)
233 return fctx.data()
233 return fctx.data()
234
234
235 def get_file_size(self, path):
235 def get_file_size(self, path):
236 """
236 """
237 Returns size of the file at given ``path``.
237 Returns size of the file at given ``path``.
238 """
238 """
239 fctx = self._get_filectx(path)
239 fctx = self._get_filectx(path)
240 return fctx.size()
240 return fctx.size()
241
241
242 def get_file_changeset(self, path):
242 def get_file_changeset(self, path):
243 """
243 """
244 Returns last commit of the file at the given ``path``.
244 Returns last commit of the file at the given ``path``.
245 """
245 """
246 return self.get_file_history(path, limit=1)[0]
246 return self.get_file_history(path, limit=1)[0]
247
247
248 def get_file_history(self, path, limit=None):
248 def get_file_history(self, path, limit=None):
249 """
249 """
250 Returns history of file as reversed list of ``Changeset`` objects for
250 Returns history of file as reversed list of ``Changeset`` objects for
251 which file at given ``path`` has been modified.
251 which file at given ``path`` has been modified.
252 """
252 """
253 fctx = self._get_filectx(path)
253 fctx = self._get_filectx(path)
254 hist = []
254 hist = []
255 cnt = 0
255 cnt = 0
256 for cs in reversed([x for x in fctx.filelog()]):
256 for cs in reversed([x for x in fctx.filelog()]):
257 cnt += 1
257 cnt += 1
258 hist.append(hex(fctx.filectx(cs).node()))
258 hist.append(hex(fctx.filectx(cs).node()))
259 if limit and cnt == limit:
259 if limit is not None and cnt == limit:
260 break
260 break
261
261
262 return [self.repository.get_changeset(node) for node in hist]
262 return [self.repository.get_changeset(node) for node in hist]
263
263
264 def get_file_annotate(self, path):
264 def get_file_annotate(self, path):
265 """
265 """
266 Returns a generator of four element tuples with
266 Returns a generator of four element tuples with
267 lineno, sha, changeset lazy loader and line
267 lineno, sha, changeset lazy loader and line
268 """
268 """
269
269
270 fctx = self._get_filectx(path)
270 fctx = self._get_filectx(path)
271 for i, annotate_data in enumerate(fctx.annotate()):
271 for i, annotate_data in enumerate(fctx.annotate()):
272 ln_no = i + 1
272 ln_no = i + 1
273 sha = hex(annotate_data[0].node())
273 sha = hex(annotate_data[0].node())
274 yield (ln_no, sha, lambda: self.repository.get_changeset(sha), annotate_data[1],)
274 yield (ln_no, sha, lambda: self.repository.get_changeset(sha), annotate_data[1],)
275
275
276 def fill_archive(self, stream=None, kind='tgz', prefix=None,
276 def fill_archive(self, stream=None, kind='tgz', prefix=None,
277 subrepos=False):
277 subrepos=False):
278 """
278 """
279 Fills up given stream.
279 Fills up given stream.
280
280
281 :param stream: file like object.
281 :param stream: file like object.
282 :param kind: one of following: ``zip``, ``tgz`` or ``tbz2``.
282 :param kind: one of following: ``zip``, ``tgz`` or ``tbz2``.
283 Default: ``tgz``.
283 Default: ``tgz``.
284 :param prefix: name of root directory in archive.
284 :param prefix: name of root directory in archive.
285 Default is repository name and changeset's raw_id joined with dash
285 Default is repository name and changeset's raw_id joined with dash
286 (``repo-tip.<KIND>``).
286 (``repo-tip.<KIND>``).
287 :param subrepos: include subrepos in this archive.
287 :param subrepos: include subrepos in this archive.
288
288
289 :raise ImproperArchiveTypeError: If given kind is wrong.
289 :raise ImproperArchiveTypeError: If given kind is wrong.
290 :raise VcsError: If given stream is None
290 :raise VcsError: If given stream is None
291 """
291 """
292
292
293 allowed_kinds = settings.ARCHIVE_SPECS.keys()
293 allowed_kinds = settings.ARCHIVE_SPECS.keys()
294 if kind not in allowed_kinds:
294 if kind not in allowed_kinds:
295 raise ImproperArchiveTypeError('Archive kind not supported use one'
295 raise ImproperArchiveTypeError('Archive kind not supported use one'
296 'of %s', allowed_kinds)
296 'of %s', allowed_kinds)
297
297
298 if stream is None:
298 if stream is None:
299 raise VCSError('You need to pass in a valid stream for filling'
299 raise VCSError('You need to pass in a valid stream for filling'
300 ' with archival data')
300 ' with archival data')
301
301
302 if prefix is None:
302 if prefix is None:
303 prefix = '%s-%s' % (self.repository.name, self.short_id)
303 prefix = '%s-%s' % (self.repository.name, self.short_id)
304 elif prefix.startswith('/'):
304 elif prefix.startswith('/'):
305 raise VCSError("Prefix cannot start with leading slash")
305 raise VCSError("Prefix cannot start with leading slash")
306 elif prefix.strip() == '':
306 elif prefix.strip() == '':
307 raise VCSError("Prefix cannot be empty")
307 raise VCSError("Prefix cannot be empty")
308
308
309 archival.archive(self.repository._repo, stream, self.raw_id,
309 archival.archive(self.repository._repo, stream, self.raw_id,
310 kind, prefix=prefix, subrepos=subrepos)
310 kind, prefix=prefix, subrepos=subrepos)
311
311
312 def get_nodes(self, path):
312 def get_nodes(self, path):
313 """
313 """
314 Returns combined ``DirNode`` and ``FileNode`` objects list representing
314 Returns combined ``DirNode`` and ``FileNode`` objects list representing
315 state of changeset at the given ``path``. If node at the given ``path``
315 state of changeset at the given ``path``. If node at the given ``path``
316 is not instance of ``DirNode``, ChangesetError would be raised.
316 is not instance of ``DirNode``, ChangesetError would be raised.
317 """
317 """
318
318
319 if self._get_kind(path) != NodeKind.DIR:
319 if self._get_kind(path) != NodeKind.DIR:
320 raise ChangesetError("Directory does not exist for revision %s at "
320 raise ChangesetError("Directory does not exist for revision %s at "
321 " '%s'" % (self.revision, path))
321 " '%s'" % (self.revision, path))
322 path = self._fix_path(path)
322 path = self._fix_path(path)
323
323
324 filenodes = [FileNode(f, changeset=self) for f in self._file_paths
324 filenodes = [FileNode(f, changeset=self) for f in self._file_paths
325 if os.path.dirname(f) == path]
325 if os.path.dirname(f) == path]
326 dirs = path == '' and '' or [d for d in self._dir_paths
326 dirs = path == '' and '' or [d for d in self._dir_paths
327 if d and posixpath.dirname(d) == path]
327 if d and posixpath.dirname(d) == path]
328 dirnodes = [DirNode(d, changeset=self) for d in dirs
328 dirnodes = [DirNode(d, changeset=self) for d in dirs
329 if os.path.dirname(d) == path]
329 if os.path.dirname(d) == path]
330
330
331 als = self.repository.alias
331 als = self.repository.alias
332 for k, vals in self._extract_submodules().iteritems():
332 for k, vals in self._extract_submodules().iteritems():
333 #vals = url,rev,type
333 #vals = url,rev,type
334 loc = vals[0]
334 loc = vals[0]
335 cs = vals[1]
335 cs = vals[1]
336 dirnodes.append(SubModuleNode(k, url=loc, changeset=cs,
336 dirnodes.append(SubModuleNode(k, url=loc, changeset=cs,
337 alias=als))
337 alias=als))
338 nodes = dirnodes + filenodes
338 nodes = dirnodes + filenodes
339 # cache nodes
339 # cache nodes
340 for node in nodes:
340 for node in nodes:
341 self.nodes[node.path] = node
341 self.nodes[node.path] = node
342 nodes.sort()
342 nodes.sort()
343
343
344 return nodes
344 return nodes
345
345
346 def get_node(self, path):
346 def get_node(self, path):
347 """
347 """
348 Returns ``Node`` object from the given ``path``. If there is no node at
348 Returns ``Node`` object from the given ``path``. If there is no node at
349 the given ``path``, ``ChangesetError`` would be raised.
349 the given ``path``, ``ChangesetError`` would be raised.
350 """
350 """
351
351
352 path = self._fix_path(path)
352 path = self._fix_path(path)
353
353
354 if not path in self.nodes:
354 if not path in self.nodes:
355 if path in self._file_paths:
355 if path in self._file_paths:
356 node = FileNode(path, changeset=self)
356 node = FileNode(path, changeset=self)
357 elif path in self._dir_paths or path in self._dir_paths:
357 elif path in self._dir_paths or path in self._dir_paths:
358 if path == '':
358 if path == '':
359 node = RootNode(changeset=self)
359 node = RootNode(changeset=self)
360 else:
360 else:
361 node = DirNode(path, changeset=self)
361 node = DirNode(path, changeset=self)
362 else:
362 else:
363 raise NodeDoesNotExistError("There is no file nor directory "
363 raise NodeDoesNotExistError("There is no file nor directory "
364 "at the given path: '%s' at revision %s"
364 "at the given path: '%s' at revision %s"
365 % (path, self.short_id))
365 % (path, self.short_id))
366 # cache node
366 # cache node
367 self.nodes[path] = node
367 self.nodes[path] = node
368 return self.nodes[path]
368 return self.nodes[path]
369
369
370 @LazyProperty
370 @LazyProperty
371 def affected_files(self):
371 def affected_files(self):
372 """
372 """
373 Gets a fast accessible file changes for given changeset
373 Gets a fast accessible file changes for given changeset
374 """
374 """
375 return self._ctx.files()
375 return self._ctx.files()
376
376
377 @property
377 @property
378 def added(self):
378 def added(self):
379 """
379 """
380 Returns list of added ``FileNode`` objects.
380 Returns list of added ``FileNode`` objects.
381 """
381 """
382 return AddedFileNodesGenerator([n for n in self.status[1]], self)
382 return AddedFileNodesGenerator([n for n in self.status[1]], self)
383
383
384 @property
384 @property
385 def changed(self):
385 def changed(self):
386 """
386 """
387 Returns list of modified ``FileNode`` objects.
387 Returns list of modified ``FileNode`` objects.
388 """
388 """
389 return ChangedFileNodesGenerator([n for n in self.status[0]], self)
389 return ChangedFileNodesGenerator([n for n in self.status[0]], self)
390
390
391 @property
391 @property
392 def removed(self):
392 def removed(self):
393 """
393 """
394 Returns list of removed ``FileNode`` objects.
394 Returns list of removed ``FileNode`` objects.
395 """
395 """
396 return RemovedFileNodesGenerator([n for n in self.status[2]], self)
396 return RemovedFileNodesGenerator([n for n in self.status[2]], self)
397
397
398 @LazyProperty
398 @LazyProperty
399 def extra(self):
399 def extra(self):
400 return self._ctx.extra()
400 return self._ctx.extra()
@@ -1,771 +1,771 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 # This program is free software: you can redistribute it and/or modify
2 # This program is free software: you can redistribute it and/or modify
3 # it under the terms of the GNU General Public License as published by
3 # it under the terms of the GNU General Public License as published by
4 # the Free Software Foundation, either version 3 of the License, or
4 # the Free Software Foundation, either version 3 of the License, or
5 # (at your option) any later version.
5 # (at your option) any later version.
6 #
6 #
7 # This program is distributed in the hope that it will be useful,
7 # This program is distributed in the hope that it will be useful,
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
8 # but WITHOUT ANY WARRANTY; without even the implied warranty of
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
9 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 # GNU General Public License for more details.
10 # GNU General Public License for more details.
11 #
11 #
12 # You should have received a copy of the GNU General Public License
12 # You should have received a copy of the GNU General Public License
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
13 # along with this program. If not, see <http://www.gnu.org/licenses/>.
14 """
14 """
15 kallithea.model.repo
15 kallithea.model.repo
16 ~~~~~~~~~~~~~~~~~~~~
16 ~~~~~~~~~~~~~~~~~~~~
17
17
18 Repository model for kallithea
18 Repository model for kallithea
19
19
20 This file was forked by the Kallithea project in July 2014.
20 This file was forked by the Kallithea project in July 2014.
21 Original author and date, and relevant copyright and licensing information is below:
21 Original author and date, and relevant copyright and licensing information is below:
22 :created_on: Jun 5, 2010
22 :created_on: Jun 5, 2010
23 :author: marcink
23 :author: marcink
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
24 :copyright: (c) 2013 RhodeCode GmbH, and others.
25 :license: GPLv3, see LICENSE.md for more details.
25 :license: GPLv3, see LICENSE.md for more details.
26
26
27 """
27 """
28
28
29 import os
29 import os
30 import shutil
30 import shutil
31 import logging
31 import logging
32 import traceback
32 import traceback
33 from datetime import datetime
33 from datetime import datetime
34 from sqlalchemy.orm import subqueryload
34 from sqlalchemy.orm import subqueryload
35
35
36 from kallithea.lib.utils import make_ui
36 from kallithea.lib.utils import make_ui
37 from kallithea.lib.vcs.backends import get_backend
37 from kallithea.lib.vcs.backends import get_backend
38 from kallithea.lib.compat import json
38 from kallithea.lib.compat import json
39 from kallithea.lib.utils2 import LazyProperty, safe_str, safe_unicode, \
39 from kallithea.lib.utils2 import LazyProperty, safe_str, safe_unicode, \
40 remove_prefix, obfuscate_url_pw, get_current_authuser
40 remove_prefix, obfuscate_url_pw, get_current_authuser
41 from kallithea.lib.caching_query import FromCache
41 from kallithea.lib.caching_query import FromCache
42 from kallithea.lib.hooks import log_delete_repository
42 from kallithea.lib.hooks import log_delete_repository
43
43
44 from kallithea.model import BaseModel
44 from kallithea.model import BaseModel
45 from kallithea.model.db import Repository, UserRepoToPerm, UserGroupRepoToPerm, \
45 from kallithea.model.db import Repository, UserRepoToPerm, UserGroupRepoToPerm, \
46 UserRepoGroupToPerm, UserGroupRepoGroupToPerm, User, Permission, \
46 UserRepoGroupToPerm, UserGroupRepoGroupToPerm, User, Permission, \
47 Statistics, UserGroup, Ui, RepoGroup, RepositoryField
47 Statistics, UserGroup, Ui, RepoGroup, RepositoryField
48
48
49 from kallithea.lib import helpers as h
49 from kallithea.lib import helpers as h
50 from kallithea.lib.auth import HasRepoPermissionAny, HasUserGroupPermissionAny
50 from kallithea.lib.auth import HasRepoPermissionAny, HasUserGroupPermissionAny
51 from kallithea.lib.exceptions import AttachedForksError
51 from kallithea.lib.exceptions import AttachedForksError
52 from kallithea.model.scm import UserGroupList
52 from kallithea.model.scm import UserGroupList
53
53
54 log = logging.getLogger(__name__)
54 log = logging.getLogger(__name__)
55
55
56
56
57 class RepoModel(BaseModel):
57 class RepoModel(BaseModel):
58
58
59 cls = Repository
59 cls = Repository
60 URL_SEPARATOR = Repository.url_sep()
60 URL_SEPARATOR = Repository.url_sep()
61
61
62 def _get_user_group(self, users_group):
62 def _get_user_group(self, users_group):
63 return self._get_instance(UserGroup, users_group,
63 return self._get_instance(UserGroup, users_group,
64 callback=UserGroup.get_by_group_name)
64 callback=UserGroup.get_by_group_name)
65
65
66 def _get_repo_group(self, repo_group):
66 def _get_repo_group(self, repo_group):
67 return self._get_instance(RepoGroup, repo_group,
67 return self._get_instance(RepoGroup, repo_group,
68 callback=RepoGroup.get_by_group_name)
68 callback=RepoGroup.get_by_group_name)
69
69
70 def _create_default_perms(self, repository, private):
70 def _create_default_perms(self, repository, private):
71 # create default permission
71 # create default permission
72 default = 'repository.read'
72 default = 'repository.read'
73 def_user = User.get_default_user()
73 def_user = User.get_default_user()
74 for p in def_user.user_perms:
74 for p in def_user.user_perms:
75 if p.permission.permission_name.startswith('repository.'):
75 if p.permission.permission_name.startswith('repository.'):
76 default = p.permission.permission_name
76 default = p.permission.permission_name
77 break
77 break
78
78
79 default_perm = 'repository.none' if private else default
79 default_perm = 'repository.none' if private else default
80
80
81 repo_to_perm = UserRepoToPerm()
81 repo_to_perm = UserRepoToPerm()
82 repo_to_perm.permission = Permission.get_by_key(default_perm)
82 repo_to_perm.permission = Permission.get_by_key(default_perm)
83
83
84 repo_to_perm.repository = repository
84 repo_to_perm.repository = repository
85 repo_to_perm.user_id = def_user.user_id
85 repo_to_perm.user_id = def_user.user_id
86
86
87 return repo_to_perm
87 return repo_to_perm
88
88
89 @LazyProperty
89 @LazyProperty
90 def repos_path(self):
90 def repos_path(self):
91 """
91 """
92 Gets the repositories root path from database
92 Gets the repositories root path from database
93 """
93 """
94
94
95 q = self.sa.query(Ui).filter(Ui.ui_key == '/').one()
95 q = self.sa.query(Ui).filter(Ui.ui_key == '/').one()
96 return q.ui_value
96 return q.ui_value
97
97
98 def get(self, repo_id, cache=False):
98 def get(self, repo_id, cache=False):
99 repo = self.sa.query(Repository) \
99 repo = self.sa.query(Repository) \
100 .filter(Repository.repo_id == repo_id)
100 .filter(Repository.repo_id == repo_id)
101
101
102 if cache:
102 if cache:
103 repo = repo.options(FromCache("sql_cache_short",
103 repo = repo.options(FromCache("sql_cache_short",
104 "get_repo_%s" % repo_id))
104 "get_repo_%s" % repo_id))
105 return repo.scalar()
105 return repo.scalar()
106
106
107 def get_repo(self, repository):
107 def get_repo(self, repository):
108 return self._get_repo(repository)
108 return self._get_repo(repository)
109
109
110 def get_by_repo_name(self, repo_name, cache=False):
110 def get_by_repo_name(self, repo_name, cache=False):
111 repo = self.sa.query(Repository) \
111 repo = self.sa.query(Repository) \
112 .filter(Repository.repo_name == repo_name)
112 .filter(Repository.repo_name == repo_name)
113
113
114 if cache:
114 if cache:
115 repo = repo.options(FromCache("sql_cache_short",
115 repo = repo.options(FromCache("sql_cache_short",
116 "get_repo_%s" % repo_name))
116 "get_repo_%s" % repo_name))
117 return repo.scalar()
117 return repo.scalar()
118
118
119 def get_all_user_repos(self, user):
119 def get_all_user_repos(self, user):
120 """
120 """
121 Gets all repositories that user have at least read access
121 Gets all repositories that user have at least read access
122
122
123 :param user:
123 :param user:
124 """
124 """
125 from kallithea.lib.auth import AuthUser
125 from kallithea.lib.auth import AuthUser
126 user = self._get_user(user)
126 user = self._get_user(user)
127 repos = AuthUser(user_id=user.user_id).permissions['repositories']
127 repos = AuthUser(user_id=user.user_id).permissions['repositories']
128 access_check = lambda r: r[1] in ['repository.read',
128 access_check = lambda r: r[1] in ['repository.read',
129 'repository.write',
129 'repository.write',
130 'repository.admin']
130 'repository.admin']
131 repos = [x[0] for x in filter(access_check, repos.items())]
131 repos = [x[0] for x in filter(access_check, repos.items())]
132 return Repository.query().filter(Repository.repo_name.in_(repos))
132 return Repository.query().filter(Repository.repo_name.in_(repos))
133
133
134 def get_users_js(self):
134 def get_users_js(self):
135 users = self.sa.query(User).filter(User.active == True).all()
135 users = self.sa.query(User).filter(User.active == True).all()
136 return json.dumps([
136 return json.dumps([
137 {
137 {
138 'id': u.user_id,
138 'id': u.user_id,
139 'fname': h.escape(u.name),
139 'fname': h.escape(u.name),
140 'lname': h.escape(u.lastname),
140 'lname': h.escape(u.lastname),
141 'nname': u.username,
141 'nname': u.username,
142 'gravatar_lnk': h.gravatar_url(u.email, size=28),
142 'gravatar_lnk': h.gravatar_url(u.email, size=28),
143 'gravatar_size': 14,
143 'gravatar_size': 14,
144 } for u in users]
144 } for u in users]
145 )
145 )
146
146
147 def get_user_groups_js(self):
147 def get_user_groups_js(self):
148 user_groups = self.sa.query(UserGroup) \
148 user_groups = self.sa.query(UserGroup) \
149 .filter(UserGroup.users_group_active == True) \
149 .filter(UserGroup.users_group_active == True) \
150 .options(subqueryload(UserGroup.members)) \
150 .options(subqueryload(UserGroup.members)) \
151 .all()
151 .all()
152 user_groups = UserGroupList(user_groups, perm_set=['usergroup.read',
152 user_groups = UserGroupList(user_groups, perm_set=['usergroup.read',
153 'usergroup.write',
153 'usergroup.write',
154 'usergroup.admin'])
154 'usergroup.admin'])
155 return json.dumps([
155 return json.dumps([
156 {
156 {
157 'id': gr.users_group_id,
157 'id': gr.users_group_id,
158 'grname': gr.users_group_name,
158 'grname': gr.users_group_name,
159 'grmembers': len(gr.members),
159 'grmembers': len(gr.members),
160 } for gr in user_groups]
160 } for gr in user_groups]
161 )
161 )
162
162
163 @classmethod
163 @classmethod
164 def _render_datatable(cls, tmpl, *args, **kwargs):
164 def _render_datatable(cls, tmpl, *args, **kwargs):
165 import kallithea
165 import kallithea
166 from pylons import tmpl_context as c
166 from pylons import tmpl_context as c
167 from pylons.i18n.translation import _
167 from pylons.i18n.translation import _
168
168
169 _tmpl_lookup = kallithea.CONFIG['pylons.app_globals'].mako_lookup
169 _tmpl_lookup = kallithea.CONFIG['pylons.app_globals'].mako_lookup
170 template = _tmpl_lookup.get_template('data_table/_dt_elements.html')
170 template = _tmpl_lookup.get_template('data_table/_dt_elements.html')
171
171
172 tmpl = template.get_def(tmpl)
172 tmpl = template.get_def(tmpl)
173 kwargs.update(dict(_=_, h=h, c=c))
173 kwargs.update(dict(_=_, h=h, c=c))
174 return tmpl.render(*args, **kwargs)
174 return tmpl.render(*args, **kwargs)
175
175
176 @classmethod
176 @classmethod
177 def update_repoinfo(cls, repositories=None):
177 def update_repoinfo(cls, repositories=None):
178 if not repositories:
178 if repositories is None:
179 repositories = Repository.getAll()
179 repositories = Repository.getAll()
180 for repo in repositories:
180 for repo in repositories:
181 repo.update_changeset_cache()
181 repo.update_changeset_cache()
182
182
183 def get_repos_as_dict(self, repos_list=None, admin=False, perm_check=True,
183 def get_repos_as_dict(self, repos_list=None, admin=False, perm_check=True,
184 super_user_actions=False):
184 super_user_actions=False):
185 _render = self._render_datatable
185 _render = self._render_datatable
186 from pylons import tmpl_context as c
186 from pylons import tmpl_context as c
187
187
188 def quick_menu(repo_name):
188 def quick_menu(repo_name):
189 return _render('quick_menu', repo_name)
189 return _render('quick_menu', repo_name)
190
190
191 def repo_lnk(name, rtype, rstate, private, fork_of):
191 def repo_lnk(name, rtype, rstate, private, fork_of):
192 return _render('repo_name', name, rtype, rstate, private, fork_of,
192 return _render('repo_name', name, rtype, rstate, private, fork_of,
193 short_name=not admin, admin=False)
193 short_name=not admin, admin=False)
194
194
195 def last_change(last_change):
195 def last_change(last_change):
196 return _render("last_change", last_change)
196 return _render("last_change", last_change)
197
197
198 def rss_lnk(repo_name):
198 def rss_lnk(repo_name):
199 return _render("rss", repo_name)
199 return _render("rss", repo_name)
200
200
201 def atom_lnk(repo_name):
201 def atom_lnk(repo_name):
202 return _render("atom", repo_name)
202 return _render("atom", repo_name)
203
203
204 def last_rev(repo_name, cs_cache):
204 def last_rev(repo_name, cs_cache):
205 return _render('revision', repo_name, cs_cache.get('revision'),
205 return _render('revision', repo_name, cs_cache.get('revision'),
206 cs_cache.get('raw_id'), cs_cache.get('author'),
206 cs_cache.get('raw_id'), cs_cache.get('author'),
207 cs_cache.get('message'))
207 cs_cache.get('message'))
208
208
209 def desc(desc):
209 def desc(desc):
210 return h.urlify_text(desc, truncate=60, stylize=c.visual.stylify_metatags)
210 return h.urlify_text(desc, truncate=60, stylize=c.visual.stylify_metatags)
211
211
212 def state(repo_state):
212 def state(repo_state):
213 return _render("repo_state", repo_state)
213 return _render("repo_state", repo_state)
214
214
215 def repo_actions(repo_name):
215 def repo_actions(repo_name):
216 return _render('repo_actions', repo_name, super_user_actions)
216 return _render('repo_actions', repo_name, super_user_actions)
217
217
218 def owner_actions(user_id, username):
218 def owner_actions(user_id, username):
219 return _render('user_name', user_id, username)
219 return _render('user_name', user_id, username)
220
220
221 repos_data = []
221 repos_data = []
222 for repo in repos_list:
222 for repo in repos_list:
223 if perm_check:
223 if perm_check:
224 # check permission at this level
224 # check permission at this level
225 if not HasRepoPermissionAny(
225 if not HasRepoPermissionAny(
226 'repository.read', 'repository.write',
226 'repository.read', 'repository.write',
227 'repository.admin'
227 'repository.admin'
228 )(repo.repo_name, 'get_repos_as_dict check'):
228 )(repo.repo_name, 'get_repos_as_dict check'):
229 continue
229 continue
230 cs_cache = repo.changeset_cache
230 cs_cache = repo.changeset_cache
231 row = {
231 row = {
232 "menu": quick_menu(repo.repo_name),
232 "menu": quick_menu(repo.repo_name),
233 "raw_name": repo.repo_name.lower(),
233 "raw_name": repo.repo_name.lower(),
234 "name": repo_lnk(repo.repo_name, repo.repo_type,
234 "name": repo_lnk(repo.repo_name, repo.repo_type,
235 repo.repo_state, repo.private, repo.fork),
235 repo.repo_state, repo.private, repo.fork),
236 "last_change": last_change(repo.last_db_change),
236 "last_change": last_change(repo.last_db_change),
237 "last_changeset": last_rev(repo.repo_name, cs_cache),
237 "last_changeset": last_rev(repo.repo_name, cs_cache),
238 "last_rev_raw": cs_cache.get('revision'),
238 "last_rev_raw": cs_cache.get('revision'),
239 "desc": desc(repo.description),
239 "desc": desc(repo.description),
240 "owner": h.person(repo.user),
240 "owner": h.person(repo.user),
241 "state": state(repo.repo_state),
241 "state": state(repo.repo_state),
242 "rss": rss_lnk(repo.repo_name),
242 "rss": rss_lnk(repo.repo_name),
243 "atom": atom_lnk(repo.repo_name),
243 "atom": atom_lnk(repo.repo_name),
244
244
245 }
245 }
246 if admin:
246 if admin:
247 row.update({
247 row.update({
248 "action": repo_actions(repo.repo_name),
248 "action": repo_actions(repo.repo_name),
249 "owner": owner_actions(repo.user.user_id,
249 "owner": owner_actions(repo.user.user_id,
250 h.person(repo.user))
250 h.person(repo.user))
251 })
251 })
252 repos_data.append(row)
252 repos_data.append(row)
253
253
254 return {
254 return {
255 "totalRecords": len(repos_list),
255 "totalRecords": len(repos_list),
256 "startIndex": 0,
256 "startIndex": 0,
257 "sort": "name",
257 "sort": "name",
258 "dir": "asc",
258 "dir": "asc",
259 "records": repos_data
259 "records": repos_data
260 }
260 }
261
261
262 def _get_defaults(self, repo_name):
262 def _get_defaults(self, repo_name):
263 """
263 """
264 Gets information about repository, and returns a dict for
264 Gets information about repository, and returns a dict for
265 usage in forms
265 usage in forms
266
266
267 :param repo_name:
267 :param repo_name:
268 """
268 """
269
269
270 repo_info = Repository.get_by_repo_name(repo_name)
270 repo_info = Repository.get_by_repo_name(repo_name)
271
271
272 if repo_info is None:
272 if repo_info is None:
273 return None
273 return None
274
274
275 defaults = repo_info.get_dict()
275 defaults = repo_info.get_dict()
276 group, repo_name, repo_name_full = repo_info.groups_and_repo
276 group, repo_name, repo_name_full = repo_info.groups_and_repo
277 defaults['repo_name'] = repo_name
277 defaults['repo_name'] = repo_name
278 defaults['repo_group'] = getattr(group[-1] if group else None,
278 defaults['repo_group'] = getattr(group[-1] if group else None,
279 'group_id', None)
279 'group_id', None)
280
280
281 for strip, k in [(0, 'repo_type'), (1, 'repo_enable_downloads'),
281 for strip, k in [(0, 'repo_type'), (1, 'repo_enable_downloads'),
282 (1, 'repo_description'), (1, 'repo_enable_locking'),
282 (1, 'repo_description'), (1, 'repo_enable_locking'),
283 (1, 'repo_landing_rev'), (0, 'clone_uri'),
283 (1, 'repo_landing_rev'), (0, 'clone_uri'),
284 (1, 'repo_private'), (1, 'repo_enable_statistics')]:
284 (1, 'repo_private'), (1, 'repo_enable_statistics')]:
285 attr = k
285 attr = k
286 if strip:
286 if strip:
287 attr = remove_prefix(k, 'repo_')
287 attr = remove_prefix(k, 'repo_')
288
288
289 val = defaults[attr]
289 val = defaults[attr]
290 if k == 'repo_landing_rev':
290 if k == 'repo_landing_rev':
291 val = ':'.join(defaults[attr])
291 val = ':'.join(defaults[attr])
292 defaults[k] = val
292 defaults[k] = val
293 if k == 'clone_uri':
293 if k == 'clone_uri':
294 defaults['clone_uri_hidden'] = repo_info.clone_uri_hidden
294 defaults['clone_uri_hidden'] = repo_info.clone_uri_hidden
295
295
296 # fill owner
296 # fill owner
297 if repo_info.user:
297 if repo_info.user:
298 defaults.update({'user': repo_info.user.username})
298 defaults.update({'user': repo_info.user.username})
299 else:
299 else:
300 replacement_user = User.query().filter(User.admin ==
300 replacement_user = User.query().filter(User.admin ==
301 True).first().username
301 True).first().username
302 defaults.update({'user': replacement_user})
302 defaults.update({'user': replacement_user})
303
303
304 # fill repository users
304 # fill repository users
305 for p in repo_info.repo_to_perm:
305 for p in repo_info.repo_to_perm:
306 defaults.update({'u_perm_%s' % p.user.username:
306 defaults.update({'u_perm_%s' % p.user.username:
307 p.permission.permission_name})
307 p.permission.permission_name})
308
308
309 # fill repository groups
309 # fill repository groups
310 for p in repo_info.users_group_to_perm:
310 for p in repo_info.users_group_to_perm:
311 defaults.update({'g_perm_%s' % p.users_group.users_group_name:
311 defaults.update({'g_perm_%s' % p.users_group.users_group_name:
312 p.permission.permission_name})
312 p.permission.permission_name})
313
313
314 return defaults
314 return defaults
315
315
316 def update(self, repo, **kwargs):
316 def update(self, repo, **kwargs):
317 try:
317 try:
318 cur_repo = self._get_repo(repo)
318 cur_repo = self._get_repo(repo)
319 org_repo_name = cur_repo.repo_name
319 org_repo_name = cur_repo.repo_name
320 if 'user' in kwargs:
320 if 'user' in kwargs:
321 cur_repo.user = User.get_by_username(kwargs['user'])
321 cur_repo.user = User.get_by_username(kwargs['user'])
322
322
323 if 'repo_group' in kwargs:
323 if 'repo_group' in kwargs:
324 cur_repo.group = RepoGroup.get(kwargs['repo_group'])
324 cur_repo.group = RepoGroup.get(kwargs['repo_group'])
325 log.debug('Updating repo %s with params:%s', cur_repo, kwargs)
325 log.debug('Updating repo %s with params:%s', cur_repo, kwargs)
326 for k in ['repo_enable_downloads',
326 for k in ['repo_enable_downloads',
327 'repo_description',
327 'repo_description',
328 'repo_enable_locking',
328 'repo_enable_locking',
329 'repo_landing_rev',
329 'repo_landing_rev',
330 'repo_private',
330 'repo_private',
331 'repo_enable_statistics',
331 'repo_enable_statistics',
332 ]:
332 ]:
333 if k in kwargs:
333 if k in kwargs:
334 setattr(cur_repo, remove_prefix(k, 'repo_'), kwargs[k])
334 setattr(cur_repo, remove_prefix(k, 'repo_'), kwargs[k])
335 clone_uri = kwargs.get('clone_uri')
335 clone_uri = kwargs.get('clone_uri')
336 if clone_uri is not None and clone_uri != cur_repo.clone_uri_hidden:
336 if clone_uri is not None and clone_uri != cur_repo.clone_uri_hidden:
337 cur_repo.clone_uri = clone_uri
337 cur_repo.clone_uri = clone_uri
338
338
339 new_name = cur_repo.get_new_name(kwargs['repo_name'])
339 new_name = cur_repo.get_new_name(kwargs['repo_name'])
340 cur_repo.repo_name = new_name
340 cur_repo.repo_name = new_name
341 #if private flag is set, reset default permission to NONE
341 #if private flag is set, reset default permission to NONE
342
342
343 if kwargs.get('repo_private'):
343 if kwargs.get('repo_private'):
344 EMPTY_PERM = 'repository.none'
344 EMPTY_PERM = 'repository.none'
345 RepoModel().grant_user_permission(
345 RepoModel().grant_user_permission(
346 repo=cur_repo, user='default', perm=EMPTY_PERM
346 repo=cur_repo, user='default', perm=EMPTY_PERM
347 )
347 )
348 #handle extra fields
348 #handle extra fields
349 for field in filter(lambda k: k.startswith(RepositoryField.PREFIX),
349 for field in filter(lambda k: k.startswith(RepositoryField.PREFIX),
350 kwargs):
350 kwargs):
351 k = RepositoryField.un_prefix_key(field)
351 k = RepositoryField.un_prefix_key(field)
352 ex_field = RepositoryField.get_by_key_name(key=k, repo=cur_repo)
352 ex_field = RepositoryField.get_by_key_name(key=k, repo=cur_repo)
353 if ex_field:
353 if ex_field:
354 ex_field.field_value = kwargs[field]
354 ex_field.field_value = kwargs[field]
355 self.sa.add(ex_field)
355 self.sa.add(ex_field)
356 self.sa.add(cur_repo)
356 self.sa.add(cur_repo)
357
357
358 if org_repo_name != new_name:
358 if org_repo_name != new_name:
359 # rename repository
359 # rename repository
360 self._rename_filesystem_repo(old=org_repo_name, new=new_name)
360 self._rename_filesystem_repo(old=org_repo_name, new=new_name)
361
361
362 return cur_repo
362 return cur_repo
363 except Exception:
363 except Exception:
364 log.error(traceback.format_exc())
364 log.error(traceback.format_exc())
365 raise
365 raise
366
366
367 def _create_repo(self, repo_name, repo_type, description, owner,
367 def _create_repo(self, repo_name, repo_type, description, owner,
368 private=False, clone_uri=None, repo_group=None,
368 private=False, clone_uri=None, repo_group=None,
369 landing_rev='rev:tip', fork_of=None,
369 landing_rev='rev:tip', fork_of=None,
370 copy_fork_permissions=False, enable_statistics=False,
370 copy_fork_permissions=False, enable_statistics=False,
371 enable_locking=False, enable_downloads=False,
371 enable_locking=False, enable_downloads=False,
372 copy_group_permissions=False, state=Repository.STATE_PENDING):
372 copy_group_permissions=False, state=Repository.STATE_PENDING):
373 """
373 """
374 Create repository inside database with PENDING state. This should only be
374 Create repository inside database with PENDING state. This should only be
375 executed by create() repo, with exception of importing existing repos.
375 executed by create() repo, with exception of importing existing repos.
376
376
377 """
377 """
378 from kallithea.model.scm import ScmModel
378 from kallithea.model.scm import ScmModel
379
379
380 owner = self._get_user(owner)
380 owner = self._get_user(owner)
381 fork_of = self._get_repo(fork_of)
381 fork_of = self._get_repo(fork_of)
382 repo_group = self._get_repo_group(repo_group)
382 repo_group = self._get_repo_group(repo_group)
383 try:
383 try:
384 repo_name = safe_unicode(repo_name)
384 repo_name = safe_unicode(repo_name)
385 description = safe_unicode(description)
385 description = safe_unicode(description)
386 # repo name is just a name of repository
386 # repo name is just a name of repository
387 # while repo_name_full is a full qualified name that is combined
387 # while repo_name_full is a full qualified name that is combined
388 # with name and path of group
388 # with name and path of group
389 repo_name_full = repo_name
389 repo_name_full = repo_name
390 repo_name = repo_name.split(self.URL_SEPARATOR)[-1]
390 repo_name = repo_name.split(self.URL_SEPARATOR)[-1]
391
391
392 new_repo = Repository()
392 new_repo = Repository()
393 new_repo.repo_state = state
393 new_repo.repo_state = state
394 new_repo.enable_statistics = False
394 new_repo.enable_statistics = False
395 new_repo.repo_name = repo_name_full
395 new_repo.repo_name = repo_name_full
396 new_repo.repo_type = repo_type
396 new_repo.repo_type = repo_type
397 new_repo.user = owner
397 new_repo.user = owner
398 new_repo.group = repo_group
398 new_repo.group = repo_group
399 new_repo.description = description or repo_name
399 new_repo.description = description or repo_name
400 new_repo.private = private
400 new_repo.private = private
401 new_repo.clone_uri = clone_uri
401 new_repo.clone_uri = clone_uri
402 new_repo.landing_rev = landing_rev
402 new_repo.landing_rev = landing_rev
403
403
404 new_repo.enable_statistics = enable_statistics
404 new_repo.enable_statistics = enable_statistics
405 new_repo.enable_locking = enable_locking
405 new_repo.enable_locking = enable_locking
406 new_repo.enable_downloads = enable_downloads
406 new_repo.enable_downloads = enable_downloads
407
407
408 if repo_group:
408 if repo_group:
409 new_repo.enable_locking = repo_group.enable_locking
409 new_repo.enable_locking = repo_group.enable_locking
410
410
411 if fork_of:
411 if fork_of:
412 parent_repo = fork_of
412 parent_repo = fork_of
413 new_repo.fork = parent_repo
413 new_repo.fork = parent_repo
414
414
415 self.sa.add(new_repo)
415 self.sa.add(new_repo)
416
416
417 if fork_of and copy_fork_permissions:
417 if fork_of and copy_fork_permissions:
418 repo = fork_of
418 repo = fork_of
419 user_perms = UserRepoToPerm.query() \
419 user_perms = UserRepoToPerm.query() \
420 .filter(UserRepoToPerm.repository == repo).all()
420 .filter(UserRepoToPerm.repository == repo).all()
421 group_perms = UserGroupRepoToPerm.query() \
421 group_perms = UserGroupRepoToPerm.query() \
422 .filter(UserGroupRepoToPerm.repository == repo).all()
422 .filter(UserGroupRepoToPerm.repository == repo).all()
423
423
424 for perm in user_perms:
424 for perm in user_perms:
425 UserRepoToPerm.create(perm.user, new_repo, perm.permission)
425 UserRepoToPerm.create(perm.user, new_repo, perm.permission)
426
426
427 for perm in group_perms:
427 for perm in group_perms:
428 UserGroupRepoToPerm.create(perm.users_group, new_repo,
428 UserGroupRepoToPerm.create(perm.users_group, new_repo,
429 perm.permission)
429 perm.permission)
430
430
431 elif repo_group and copy_group_permissions:
431 elif repo_group and copy_group_permissions:
432
432
433 user_perms = UserRepoGroupToPerm.query() \
433 user_perms = UserRepoGroupToPerm.query() \
434 .filter(UserRepoGroupToPerm.group == repo_group).all()
434 .filter(UserRepoGroupToPerm.group == repo_group).all()
435
435
436 group_perms = UserGroupRepoGroupToPerm.query() \
436 group_perms = UserGroupRepoGroupToPerm.query() \
437 .filter(UserGroupRepoGroupToPerm.group == repo_group).all()
437 .filter(UserGroupRepoGroupToPerm.group == repo_group).all()
438
438
439 for perm in user_perms:
439 for perm in user_perms:
440 perm_name = perm.permission.permission_name.replace('group.', 'repository.')
440 perm_name = perm.permission.permission_name.replace('group.', 'repository.')
441 perm_obj = Permission.get_by_key(perm_name)
441 perm_obj = Permission.get_by_key(perm_name)
442 UserRepoToPerm.create(perm.user, new_repo, perm_obj)
442 UserRepoToPerm.create(perm.user, new_repo, perm_obj)
443
443
444 for perm in group_perms:
444 for perm in group_perms:
445 perm_name = perm.permission.permission_name.replace('group.', 'repository.')
445 perm_name = perm.permission.permission_name.replace('group.', 'repository.')
446 perm_obj = Permission.get_by_key(perm_name)
446 perm_obj = Permission.get_by_key(perm_name)
447 UserGroupRepoToPerm.create(perm.users_group, new_repo, perm_obj)
447 UserGroupRepoToPerm.create(perm.users_group, new_repo, perm_obj)
448
448
449 else:
449 else:
450 perm_obj = self._create_default_perms(new_repo, private)
450 perm_obj = self._create_default_perms(new_repo, private)
451 self.sa.add(perm_obj)
451 self.sa.add(perm_obj)
452
452
453 # now automatically start following this repository as owner
453 # now automatically start following this repository as owner
454 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id,
454 ScmModel(self.sa).toggle_following_repo(new_repo.repo_id,
455 owner.user_id)
455 owner.user_id)
456 # we need to flush here, in order to check if database won't
456 # we need to flush here, in order to check if database won't
457 # throw any exceptions, create filesystem dirs at the very end
457 # throw any exceptions, create filesystem dirs at the very end
458 self.sa.flush()
458 self.sa.flush()
459 return new_repo
459 return new_repo
460 except Exception:
460 except Exception:
461 log.error(traceback.format_exc())
461 log.error(traceback.format_exc())
462 raise
462 raise
463
463
464 def create(self, form_data, cur_user):
464 def create(self, form_data, cur_user):
465 """
465 """
466 Create repository using celery tasks
466 Create repository using celery tasks
467
467
468 :param form_data:
468 :param form_data:
469 :param cur_user:
469 :param cur_user:
470 """
470 """
471 from kallithea.lib.celerylib import tasks, run_task
471 from kallithea.lib.celerylib import tasks, run_task
472 return run_task(tasks.create_repo, form_data, cur_user)
472 return run_task(tasks.create_repo, form_data, cur_user)
473
473
474 def _update_permissions(self, repo, perms_new=None, perms_updates=None,
474 def _update_permissions(self, repo, perms_new=None, perms_updates=None,
475 check_perms=True):
475 check_perms=True):
476 if not perms_new:
476 if not perms_new:
477 perms_new = []
477 perms_new = []
478 if not perms_updates:
478 if not perms_updates:
479 perms_updates = []
479 perms_updates = []
480
480
481 # update permissions
481 # update permissions
482 for member, perm, member_type in perms_updates:
482 for member, perm, member_type in perms_updates:
483 if member_type == 'user':
483 if member_type == 'user':
484 # this updates existing one
484 # this updates existing one
485 self.grant_user_permission(
485 self.grant_user_permission(
486 repo=repo, user=member, perm=perm
486 repo=repo, user=member, perm=perm
487 )
487 )
488 else:
488 else:
489 #check if we have permissions to alter this usergroup
489 #check if we have permissions to alter this usergroup
490 req_perms = (
490 req_perms = (
491 'usergroup.read', 'usergroup.write', 'usergroup.admin')
491 'usergroup.read', 'usergroup.write', 'usergroup.admin')
492 if not check_perms or HasUserGroupPermissionAny(*req_perms)(
492 if not check_perms or HasUserGroupPermissionAny(*req_perms)(
493 member):
493 member):
494 self.grant_user_group_permission(
494 self.grant_user_group_permission(
495 repo=repo, group_name=member, perm=perm
495 repo=repo, group_name=member, perm=perm
496 )
496 )
497 # set new permissions
497 # set new permissions
498 for member, perm, member_type in perms_new:
498 for member, perm, member_type in perms_new:
499 if member_type == 'user':
499 if member_type == 'user':
500 self.grant_user_permission(
500 self.grant_user_permission(
501 repo=repo, user=member, perm=perm
501 repo=repo, user=member, perm=perm
502 )
502 )
503 else:
503 else:
504 #check if we have permissions to alter this usergroup
504 #check if we have permissions to alter this usergroup
505 req_perms = (
505 req_perms = (
506 'usergroup.read', 'usergroup.write', 'usergroup.admin')
506 'usergroup.read', 'usergroup.write', 'usergroup.admin')
507 if not check_perms or HasUserGroupPermissionAny(*req_perms)(
507 if not check_perms or HasUserGroupPermissionAny(*req_perms)(
508 member):
508 member):
509 self.grant_user_group_permission(
509 self.grant_user_group_permission(
510 repo=repo, group_name=member, perm=perm
510 repo=repo, group_name=member, perm=perm
511 )
511 )
512
512
513 def create_fork(self, form_data, cur_user):
513 def create_fork(self, form_data, cur_user):
514 """
514 """
515 Simple wrapper into executing celery task for fork creation
515 Simple wrapper into executing celery task for fork creation
516
516
517 :param form_data:
517 :param form_data:
518 :param cur_user:
518 :param cur_user:
519 """
519 """
520 from kallithea.lib.celerylib import tasks, run_task
520 from kallithea.lib.celerylib import tasks, run_task
521 return run_task(tasks.create_repo_fork, form_data, cur_user)
521 return run_task(tasks.create_repo_fork, form_data, cur_user)
522
522
523 def delete(self, repo, forks=None, fs_remove=True, cur_user=None):
523 def delete(self, repo, forks=None, fs_remove=True, cur_user=None):
524 """
524 """
525 Delete given repository, forks parameter defines what do do with
525 Delete given repository, forks parameter defines what do do with
526 attached forks. Throws AttachedForksError if deleted repo has attached
526 attached forks. Throws AttachedForksError if deleted repo has attached
527 forks
527 forks
528
528
529 :param repo:
529 :param repo:
530 :param forks: str 'delete' or 'detach'
530 :param forks: str 'delete' or 'detach'
531 :param fs_remove: remove(archive) repo from filesystem
531 :param fs_remove: remove(archive) repo from filesystem
532 """
532 """
533 if not cur_user:
533 if not cur_user:
534 cur_user = getattr(get_current_authuser(), 'username', None)
534 cur_user = getattr(get_current_authuser(), 'username', None)
535 repo = self._get_repo(repo)
535 repo = self._get_repo(repo)
536 if repo is not None:
536 if repo is not None:
537 if forks == 'detach':
537 if forks == 'detach':
538 for r in repo.forks:
538 for r in repo.forks:
539 r.fork = None
539 r.fork = None
540 self.sa.add(r)
540 self.sa.add(r)
541 elif forks == 'delete':
541 elif forks == 'delete':
542 for r in repo.forks:
542 for r in repo.forks:
543 self.delete(r, forks='delete')
543 self.delete(r, forks='delete')
544 elif [f for f in repo.forks]:
544 elif [f for f in repo.forks]:
545 raise AttachedForksError()
545 raise AttachedForksError()
546
546
547 old_repo_dict = repo.get_dict()
547 old_repo_dict = repo.get_dict()
548 try:
548 try:
549 self.sa.delete(repo)
549 self.sa.delete(repo)
550 if fs_remove:
550 if fs_remove:
551 self._delete_filesystem_repo(repo)
551 self._delete_filesystem_repo(repo)
552 else:
552 else:
553 log.debug('skipping removal from filesystem')
553 log.debug('skipping removal from filesystem')
554 log_delete_repository(old_repo_dict,
554 log_delete_repository(old_repo_dict,
555 deleted_by=cur_user)
555 deleted_by=cur_user)
556 except Exception:
556 except Exception:
557 log.error(traceback.format_exc())
557 log.error(traceback.format_exc())
558 raise
558 raise
559
559
560 def grant_user_permission(self, repo, user, perm):
560 def grant_user_permission(self, repo, user, perm):
561 """
561 """
562 Grant permission for user on given repository, or update existing one
562 Grant permission for user on given repository, or update existing one
563 if found
563 if found
564
564
565 :param repo: Instance of Repository, repository_id, or repository name
565 :param repo: Instance of Repository, repository_id, or repository name
566 :param user: Instance of User, user_id or username
566 :param user: Instance of User, user_id or username
567 :param perm: Instance of Permission, or permission_name
567 :param perm: Instance of Permission, or permission_name
568 """
568 """
569 user = self._get_user(user)
569 user = self._get_user(user)
570 repo = self._get_repo(repo)
570 repo = self._get_repo(repo)
571 permission = self._get_perm(perm)
571 permission = self._get_perm(perm)
572
572
573 # check if we have that permission already
573 # check if we have that permission already
574 obj = self.sa.query(UserRepoToPerm) \
574 obj = self.sa.query(UserRepoToPerm) \
575 .filter(UserRepoToPerm.user == user) \
575 .filter(UserRepoToPerm.user == user) \
576 .filter(UserRepoToPerm.repository == repo) \
576 .filter(UserRepoToPerm.repository == repo) \
577 .scalar()
577 .scalar()
578 if obj is None:
578 if obj is None:
579 # create new !
579 # create new !
580 obj = UserRepoToPerm()
580 obj = UserRepoToPerm()
581 obj.repository = repo
581 obj.repository = repo
582 obj.user = user
582 obj.user = user
583 obj.permission = permission
583 obj.permission = permission
584 self.sa.add(obj)
584 self.sa.add(obj)
585 log.debug('Granted perm %s to %s on %s', perm, user, repo)
585 log.debug('Granted perm %s to %s on %s', perm, user, repo)
586 return obj
586 return obj
587
587
588 def revoke_user_permission(self, repo, user):
588 def revoke_user_permission(self, repo, user):
589 """
589 """
590 Revoke permission for user on given repository
590 Revoke permission for user on given repository
591
591
592 :param repo: Instance of Repository, repository_id, or repository name
592 :param repo: Instance of Repository, repository_id, or repository name
593 :param user: Instance of User, user_id or username
593 :param user: Instance of User, user_id or username
594 """
594 """
595
595
596 user = self._get_user(user)
596 user = self._get_user(user)
597 repo = self._get_repo(repo)
597 repo = self._get_repo(repo)
598
598
599 obj = self.sa.query(UserRepoToPerm) \
599 obj = self.sa.query(UserRepoToPerm) \
600 .filter(UserRepoToPerm.repository == repo) \
600 .filter(UserRepoToPerm.repository == repo) \
601 .filter(UserRepoToPerm.user == user) \
601 .filter(UserRepoToPerm.user == user) \
602 .scalar()
602 .scalar()
603 if obj is not None:
603 if obj is not None:
604 self.sa.delete(obj)
604 self.sa.delete(obj)
605 log.debug('Revoked perm on %s on %s', repo, user)
605 log.debug('Revoked perm on %s on %s', repo, user)
606
606
607 def grant_user_group_permission(self, repo, group_name, perm):
607 def grant_user_group_permission(self, repo, group_name, perm):
608 """
608 """
609 Grant permission for user group on given repository, or update
609 Grant permission for user group on given repository, or update
610 existing one if found
610 existing one if found
611
611
612 :param repo: Instance of Repository, repository_id, or repository name
612 :param repo: Instance of Repository, repository_id, or repository name
613 :param group_name: Instance of UserGroup, users_group_id,
613 :param group_name: Instance of UserGroup, users_group_id,
614 or user group name
614 or user group name
615 :param perm: Instance of Permission, or permission_name
615 :param perm: Instance of Permission, or permission_name
616 """
616 """
617 repo = self._get_repo(repo)
617 repo = self._get_repo(repo)
618 group_name = self._get_user_group(group_name)
618 group_name = self._get_user_group(group_name)
619 permission = self._get_perm(perm)
619 permission = self._get_perm(perm)
620
620
621 # check if we have that permission already
621 # check if we have that permission already
622 obj = self.sa.query(UserGroupRepoToPerm) \
622 obj = self.sa.query(UserGroupRepoToPerm) \
623 .filter(UserGroupRepoToPerm.users_group == group_name) \
623 .filter(UserGroupRepoToPerm.users_group == group_name) \
624 .filter(UserGroupRepoToPerm.repository == repo) \
624 .filter(UserGroupRepoToPerm.repository == repo) \
625 .scalar()
625 .scalar()
626
626
627 if obj is None:
627 if obj is None:
628 # create new
628 # create new
629 obj = UserGroupRepoToPerm()
629 obj = UserGroupRepoToPerm()
630
630
631 obj.repository = repo
631 obj.repository = repo
632 obj.users_group = group_name
632 obj.users_group = group_name
633 obj.permission = permission
633 obj.permission = permission
634 self.sa.add(obj)
634 self.sa.add(obj)
635 log.debug('Granted perm %s to %s on %s', perm, group_name, repo)
635 log.debug('Granted perm %s to %s on %s', perm, group_name, repo)
636 return obj
636 return obj
637
637
638 def revoke_user_group_permission(self, repo, group_name):
638 def revoke_user_group_permission(self, repo, group_name):
639 """
639 """
640 Revoke permission for user group on given repository
640 Revoke permission for user group on given repository
641
641
642 :param repo: Instance of Repository, repository_id, or repository name
642 :param repo: Instance of Repository, repository_id, or repository name
643 :param group_name: Instance of UserGroup, users_group_id,
643 :param group_name: Instance of UserGroup, users_group_id,
644 or user group name
644 or user group name
645 """
645 """
646 repo = self._get_repo(repo)
646 repo = self._get_repo(repo)
647 group_name = self._get_user_group(group_name)
647 group_name = self._get_user_group(group_name)
648
648
649 obj = self.sa.query(UserGroupRepoToPerm) \
649 obj = self.sa.query(UserGroupRepoToPerm) \
650 .filter(UserGroupRepoToPerm.repository == repo) \
650 .filter(UserGroupRepoToPerm.repository == repo) \
651 .filter(UserGroupRepoToPerm.users_group == group_name) \
651 .filter(UserGroupRepoToPerm.users_group == group_name) \
652 .scalar()
652 .scalar()
653 if obj is not None:
653 if obj is not None:
654 self.sa.delete(obj)
654 self.sa.delete(obj)
655 log.debug('Revoked perm to %s on %s', repo, group_name)
655 log.debug('Revoked perm to %s on %s', repo, group_name)
656
656
657 def delete_stats(self, repo_name):
657 def delete_stats(self, repo_name):
658 """
658 """
659 removes stats for given repo
659 removes stats for given repo
660
660
661 :param repo_name:
661 :param repo_name:
662 """
662 """
663 repo = self._get_repo(repo_name)
663 repo = self._get_repo(repo_name)
664 try:
664 try:
665 obj = self.sa.query(Statistics) \
665 obj = self.sa.query(Statistics) \
666 .filter(Statistics.repository == repo).scalar()
666 .filter(Statistics.repository == repo).scalar()
667 if obj is not None:
667 if obj is not None:
668 self.sa.delete(obj)
668 self.sa.delete(obj)
669 except Exception:
669 except Exception:
670 log.error(traceback.format_exc())
670 log.error(traceback.format_exc())
671 raise
671 raise
672
672
673 def _create_filesystem_repo(self, repo_name, repo_type, repo_group,
673 def _create_filesystem_repo(self, repo_name, repo_type, repo_group,
674 clone_uri=None, repo_store_location=None):
674 clone_uri=None, repo_store_location=None):
675 """
675 """
676 Makes repository on filesystem. Operation is group aware, meaning that it will create
676 Makes repository on filesystem. Operation is group aware, meaning that it will create
677 a repository within a group, and alter the paths accordingly to the group location.
677 a repository within a group, and alter the paths accordingly to the group location.
678
678
679 :param repo_name:
679 :param repo_name:
680 :param alias:
680 :param alias:
681 :param parent:
681 :param parent:
682 :param clone_uri:
682 :param clone_uri:
683 :param repo_store_location:
683 :param repo_store_location:
684 """
684 """
685 from kallithea.lib.utils import is_valid_repo, is_valid_repo_group
685 from kallithea.lib.utils import is_valid_repo, is_valid_repo_group
686 from kallithea.model.scm import ScmModel
686 from kallithea.model.scm import ScmModel
687
687
688 if '/' in repo_name:
688 if '/' in repo_name:
689 raise ValueError('repo_name must not contain groups got `%s`' % repo_name)
689 raise ValueError('repo_name must not contain groups got `%s`' % repo_name)
690
690
691 if isinstance(repo_group, RepoGroup):
691 if isinstance(repo_group, RepoGroup):
692 new_parent_path = os.sep.join(repo_group.full_path_splitted)
692 new_parent_path = os.sep.join(repo_group.full_path_splitted)
693 else:
693 else:
694 new_parent_path = repo_group or ''
694 new_parent_path = repo_group or ''
695
695
696 if repo_store_location:
696 if repo_store_location:
697 _paths = [repo_store_location]
697 _paths = [repo_store_location]
698 else:
698 else:
699 _paths = [self.repos_path, new_parent_path, repo_name]
699 _paths = [self.repos_path, new_parent_path, repo_name]
700 # we need to make it str for mercurial
700 # we need to make it str for mercurial
701 repo_path = os.path.join(*map(lambda x: safe_str(x), _paths))
701 repo_path = os.path.join(*map(lambda x: safe_str(x), _paths))
702
702
703 # check if this path is not a repository
703 # check if this path is not a repository
704 if is_valid_repo(repo_path, self.repos_path):
704 if is_valid_repo(repo_path, self.repos_path):
705 raise Exception('This path %s is a valid repository' % repo_path)
705 raise Exception('This path %s is a valid repository' % repo_path)
706
706
707 # check if this path is a group
707 # check if this path is a group
708 if is_valid_repo_group(repo_path, self.repos_path):
708 if is_valid_repo_group(repo_path, self.repos_path):
709 raise Exception('This path %s is a valid group' % repo_path)
709 raise Exception('This path %s is a valid group' % repo_path)
710
710
711 log.info('creating repo %s in %s from url: `%s`',
711 log.info('creating repo %s in %s from url: `%s`',
712 repo_name, safe_unicode(repo_path),
712 repo_name, safe_unicode(repo_path),
713 obfuscate_url_pw(clone_uri))
713 obfuscate_url_pw(clone_uri))
714
714
715 backend = get_backend(repo_type)
715 backend = get_backend(repo_type)
716
716
717 if repo_type == 'hg':
717 if repo_type == 'hg':
718 baseui = make_ui('db', clear_session=False)
718 baseui = make_ui('db', clear_session=False)
719 # patch and reset hooks section of UI config to not run any
719 # patch and reset hooks section of UI config to not run any
720 # hooks on creating remote repo
720 # hooks on creating remote repo
721 for k, v in baseui.configitems('hooks'):
721 for k, v in baseui.configitems('hooks'):
722 baseui.setconfig('hooks', k, None)
722 baseui.setconfig('hooks', k, None)
723
723
724 repo = backend(repo_path, create=True, src_url=clone_uri, baseui=baseui)
724 repo = backend(repo_path, create=True, src_url=clone_uri, baseui=baseui)
725 elif repo_type == 'git':
725 elif repo_type == 'git':
726 repo = backend(repo_path, create=True, src_url=clone_uri, bare=True)
726 repo = backend(repo_path, create=True, src_url=clone_uri, bare=True)
727 # add kallithea hook into this repo
727 # add kallithea hook into this repo
728 ScmModel().install_git_hooks(repo=repo)
728 ScmModel().install_git_hooks(repo=repo)
729 else:
729 else:
730 raise Exception('Not supported repo_type %s expected hg/git' % repo_type)
730 raise Exception('Not supported repo_type %s expected hg/git' % repo_type)
731
731
732 log.debug('Created repo %s with %s backend',
732 log.debug('Created repo %s with %s backend',
733 safe_unicode(repo_name), safe_unicode(repo_type))
733 safe_unicode(repo_name), safe_unicode(repo_type))
734 return repo
734 return repo
735
735
736 def _rename_filesystem_repo(self, old, new):
736 def _rename_filesystem_repo(self, old, new):
737 """
737 """
738 renames repository on filesystem
738 renames repository on filesystem
739
739
740 :param old: old name
740 :param old: old name
741 :param new: new name
741 :param new: new name
742 """
742 """
743 log.info('renaming repo from %s to %s', old, new)
743 log.info('renaming repo from %s to %s', old, new)
744
744
745 old_path = os.path.join(self.repos_path, old)
745 old_path = os.path.join(self.repos_path, old)
746 new_path = os.path.join(self.repos_path, new)
746 new_path = os.path.join(self.repos_path, new)
747 if os.path.isdir(new_path):
747 if os.path.isdir(new_path):
748 raise Exception(
748 raise Exception(
749 'Was trying to rename to already existing dir %s' % new_path
749 'Was trying to rename to already existing dir %s' % new_path
750 )
750 )
751 shutil.move(old_path, new_path)
751 shutil.move(old_path, new_path)
752
752
753 def _delete_filesystem_repo(self, repo):
753 def _delete_filesystem_repo(self, repo):
754 """
754 """
755 removes repo from filesystem, the removal is actually done by
755 removes repo from filesystem, the removal is actually done by
756 renaming dir to a 'rm__*' prefix which Kallithea will skip.
756 renaming dir to a 'rm__*' prefix which Kallithea will skip.
757 It can be undeleted later by reverting the rename.
757 It can be undeleted later by reverting the rename.
758
758
759 :param repo: repo object
759 :param repo: repo object
760 """
760 """
761 rm_path = os.path.join(self.repos_path, repo.repo_name)
761 rm_path = os.path.join(self.repos_path, repo.repo_name)
762 log.info("Removing %s", rm_path)
762 log.info("Removing %s", rm_path)
763
763
764 _now = datetime.now()
764 _now = datetime.now()
765 _ms = str(_now.microsecond).rjust(6, '0')
765 _ms = str(_now.microsecond).rjust(6, '0')
766 _d = 'rm__%s__%s' % (_now.strftime('%Y%m%d_%H%M%S_' + _ms),
766 _d = 'rm__%s__%s' % (_now.strftime('%Y%m%d_%H%M%S_' + _ms),
767 repo.just_name)
767 repo.just_name)
768 if repo.group:
768 if repo.group:
769 args = repo.group.full_path_splitted + [_d]
769 args = repo.group.full_path_splitted + [_d]
770 _d = os.path.join(*args)
770 _d = os.path.join(*args)
771 shutil.move(rm_path, os.path.join(self.repos_path, _d))
771 shutil.move(rm_path, os.path.join(self.repos_path, _d))
General Comments 0
You need to be logged in to leave comments. Login now