##// END OF EJS Templates
mercurial: use commit_ids for parents/children instead of revisions which could change when evolve is used.
marcink -
r3935:d6d0e7ad default
parent child Browse files
Show More
@@ -1,389 +1,389 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2014-2019 RhodeCode GmbH
4 4 #
5 5 # This program is free software: you can redistribute it and/or modify
6 6 # it under the terms of the GNU Affero General Public License, version 3
7 7 # (only), as published by the Free Software Foundation.
8 8 #
9 9 # This program is distributed in the hope that it will be useful,
10 10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 12 # GNU General Public License for more details.
13 13 #
14 14 # You should have received a copy of the GNU Affero General Public License
15 15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16 #
17 17 # This program is dual-licensed. If you wish to learn more about the
18 18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20 20
21 21 """
22 22 HG commit module
23 23 """
24 24
25 25 import os
26 26
27 27 from zope.cachedescriptors.property import Lazy as LazyProperty
28 28
29 29 from rhodecode.lib.datelib import utcdate_fromtimestamp
30 30 from rhodecode.lib.utils import safe_str, safe_unicode
31 31 from rhodecode.lib.vcs import path as vcspath
32 32 from rhodecode.lib.vcs.backends import base
33 33 from rhodecode.lib.vcs.backends.hg.diff import MercurialDiff
34 34 from rhodecode.lib.vcs.exceptions import CommitError
35 35 from rhodecode.lib.vcs.nodes import (
36 36 AddedFileNodesGenerator, ChangedFileNodesGenerator, DirNode, FileNode,
37 37 NodeKind, RemovedFileNodesGenerator, RootNode, SubModuleNode,
38 38 LargeFileNode, LARGEFILE_PREFIX)
39 39 from rhodecode.lib.vcs.utils.paths import get_dirs_for_path
40 40
41 41
42 42 class MercurialCommit(base.BaseCommit):
43 43 """
44 44 Represents state of the repository at the single commit.
45 45 """
46 46
47 47 _filter_pre_load = [
48 48 # git specific property not supported here
49 49 "_commit",
50 50 ]
51 51
52 52 def __init__(self, repository, raw_id, idx, pre_load=None):
53 53 raw_id = safe_str(raw_id)
54 54
55 55 self.repository = repository
56 56 self._remote = repository._remote
57 57
58 58 self.raw_id = raw_id
59 59 self.idx = idx
60 60
61 61 self._set_bulk_properties(pre_load)
62 62
63 63 # caches
64 64 self.nodes = {}
65 65
66 66 def _set_bulk_properties(self, pre_load):
67 67 if not pre_load:
68 68 return
69 69 pre_load = [entry for entry in pre_load
70 70 if entry not in self._filter_pre_load]
71 71 if not pre_load:
72 72 return
73 73
74 74 result = self._remote.bulk_request(self.raw_id, pre_load)
75 75 for attr, value in result.items():
76 76 if attr in ["author", "branch", "message"]:
77 77 value = safe_unicode(value)
78 78 elif attr == "affected_files":
79 79 value = map(safe_unicode, value)
80 80 elif attr == "date":
81 81 value = utcdate_fromtimestamp(*value)
82 82 elif attr in ["children", "parents"]:
83 83 value = self._make_commits(value)
84 84 elif attr in ["phase"]:
85 85 value = self._get_phase_text(value)
86 86 self.__dict__[attr] = value
87 87
88 88 @LazyProperty
89 89 def tags(self):
90 90 tags = [name for name, commit_id in self.repository.tags.iteritems()
91 91 if commit_id == self.raw_id]
92 92 return tags
93 93
94 94 @LazyProperty
95 95 def branch(self):
96 96 return safe_unicode(self._remote.ctx_branch(self.raw_id))
97 97
98 98 @LazyProperty
99 99 def bookmarks(self):
100 100 bookmarks = [
101 101 name for name, commit_id in self.repository.bookmarks.iteritems()
102 102 if commit_id == self.raw_id]
103 103 return bookmarks
104 104
105 105 @LazyProperty
106 106 def message(self):
107 107 return safe_unicode(self._remote.ctx_description(self.raw_id))
108 108
109 109 @LazyProperty
110 110 def committer(self):
111 111 return safe_unicode(self.author)
112 112
113 113 @LazyProperty
114 114 def author(self):
115 115 return safe_unicode(self._remote.ctx_user(self.raw_id))
116 116
117 117 @LazyProperty
118 118 def date(self):
119 119 return utcdate_fromtimestamp(*self._remote.ctx_date(self.raw_id))
120 120
121 121 @LazyProperty
122 122 def status(self):
123 123 """
124 124 Returns modified, added, removed, deleted files for current commit
125 125 """
126 126 return self._remote.ctx_status(self.raw_id)
127 127
128 128 @LazyProperty
129 129 def _file_paths(self):
130 130 return self._remote.ctx_list(self.raw_id)
131 131
132 132 @LazyProperty
133 133 def _dir_paths(self):
134 134 p = list(set(get_dirs_for_path(*self._file_paths)))
135 135 p.insert(0, '')
136 136 return p
137 137
138 138 @LazyProperty
139 139 def _paths(self):
140 140 return self._dir_paths + self._file_paths
141 141
142 142 @LazyProperty
143 143 def id(self):
144 144 if self.last:
145 145 return u'tip'
146 146 return self.short_id
147 147
148 148 @LazyProperty
149 149 def short_id(self):
150 150 return self.raw_id[:12]
151 151
152 def _make_commits(self, indexes, pre_load=None):
153 return [self.repository.get_commit(commit_idx=idx, pre_load=pre_load)
154 for idx in indexes if idx >= 0]
152 def _make_commits(self, commit_ids, pre_load=None):
153 return [self.repository.get_commit(commit_id=commit_id, pre_load=pre_load)
154 for commit_id in commit_ids]
155 155
156 156 @LazyProperty
157 157 def parents(self):
158 158 """
159 159 Returns list of parent commits.
160 160 """
161 161 parents = self._remote.ctx_parents(self.raw_id)
162 162 return self._make_commits(parents)
163 163
164 164 def _get_phase_text(self, phase_id):
165 165 return {
166 166 0: 'public',
167 167 1: 'draft',
168 168 2: 'secret',
169 169 }.get(phase_id) or ''
170 170
171 171 @LazyProperty
172 172 def phase(self):
173 173 phase_id = self._remote.ctx_phase(self.raw_id)
174 174 phase_text = self._get_phase_text(phase_id)
175 175
176 176 return safe_unicode(phase_text)
177 177
178 178 @LazyProperty
179 179 def obsolete(self):
180 180 obsolete = self._remote.ctx_obsolete(self.raw_id)
181 181 return obsolete
182 182
183 183 @LazyProperty
184 184 def hidden(self):
185 185 hidden = self._remote.ctx_hidden(self.raw_id)
186 186 return hidden
187 187
188 188 @LazyProperty
189 189 def children(self):
190 190 """
191 191 Returns list of child commits.
192 192 """
193 193 children = self._remote.ctx_children(self.raw_id)
194 194 return self._make_commits(children)
195 195
196 196 def _fix_path(self, path):
197 197 """
198 198 Mercurial keeps filenodes as str so we need to encode from unicode
199 199 to str.
200 200 """
201 201 return safe_str(super(MercurialCommit, self)._fix_path(path))
202 202
203 203 def _get_kind(self, path):
204 204 path = self._fix_path(path)
205 205 if path in self._file_paths:
206 206 return NodeKind.FILE
207 207 elif path in self._dir_paths:
208 208 return NodeKind.DIR
209 209 else:
210 210 raise CommitError(
211 211 "Node does not exist at the given path '%s'" % (path, ))
212 212
213 213 def _get_filectx(self, path):
214 214 path = self._fix_path(path)
215 215 if self._get_kind(path) != NodeKind.FILE:
216 216 raise CommitError(
217 217 "File does not exist for idx %s at '%s'" % (self.raw_id, path))
218 218 return path
219 219
220 220 def get_file_mode(self, path):
221 221 """
222 222 Returns stat mode of the file at the given ``path``.
223 223 """
224 224 path = self._get_filectx(path)
225 225 if 'x' in self._remote.fctx_flags(self.raw_id, path):
226 226 return base.FILEMODE_EXECUTABLE
227 227 else:
228 228 return base.FILEMODE_DEFAULT
229 229
230 230 def is_link(self, path):
231 231 path = self._get_filectx(path)
232 232 return 'l' in self._remote.fctx_flags(self.raw_id, path)
233 233
234 234 def is_node_binary(self, path):
235 235 path = self._get_filectx(path)
236 236 return self._remote.is_binary(self.raw_id, path)
237 237
238 238 def get_file_content(self, path):
239 239 """
240 240 Returns content of the file at given ``path``.
241 241 """
242 242 path = self._get_filectx(path)
243 243 return self._remote.fctx_node_data(self.raw_id, path)
244 244
245 245 def get_file_content_streamed(self, path):
246 246 path = self._get_filectx(path)
247 247 stream_method = getattr(self._remote, 'stream:fctx_node_data')
248 248 return stream_method(self.raw_id, path)
249 249
250 250 def get_file_size(self, path):
251 251 """
252 252 Returns size of the file at given ``path``.
253 253 """
254 254 path = self._get_filectx(path)
255 255 return self._remote.fctx_size(self.raw_id, path)
256 256
257 257 def get_path_history(self, path, limit=None, pre_load=None):
258 258 """
259 259 Returns history of file as reversed list of `MercurialCommit` objects
260 260 for which file at given ``path`` has been modified.
261 261 """
262 262 path = self._get_filectx(path)
263 263 hist = self._remote.node_history(self.raw_id, path, limit)
264 264 return [
265 265 self.repository.get_commit(commit_id=commit_id, pre_load=pre_load)
266 266 for commit_id in hist]
267 267
268 268 def get_file_annotate(self, path, pre_load=None):
269 269 """
270 270 Returns a generator of four element tuples with
271 271 lineno, commit_id, commit lazy loader and line
272 272 """
273 273 result = self._remote.fctx_annotate(self.raw_id, path)
274 274
275 275 for ln_no, commit_id, content in result:
276 276 yield (
277 277 ln_no, commit_id,
278 278 lambda: self.repository.get_commit(commit_id=commit_id, pre_load=pre_load),
279 279 content)
280 280
281 281 def get_nodes(self, path):
282 282 """
283 283 Returns combined ``DirNode`` and ``FileNode`` objects list representing
284 284 state of commit at the given ``path``. If node at the given ``path``
285 285 is not instance of ``DirNode``, CommitError would be raised.
286 286 """
287 287
288 288 if self._get_kind(path) != NodeKind.DIR:
289 289 raise CommitError(
290 290 "Directory does not exist for idx %s at '%s'" % (self.raw_id, path))
291 291 path = self._fix_path(path)
292 292
293 293 filenodes = [
294 294 FileNode(f, commit=self) for f in self._file_paths
295 295 if os.path.dirname(f) == path]
296 296 # TODO: johbo: Check if this can be done in a more obvious way
297 297 dirs = path == '' and '' or [
298 298 d for d in self._dir_paths
299 299 if d and vcspath.dirname(d) == path]
300 300 dirnodes = [
301 301 DirNode(d, commit=self) for d in dirs
302 302 if os.path.dirname(d) == path]
303 303
304 304 alias = self.repository.alias
305 305 for k, vals in self._submodules.iteritems():
306 306 if vcspath.dirname(k) == path:
307 307 loc = vals[0]
308 308 commit = vals[1]
309 309 dirnodes.append(SubModuleNode(k, url=loc, commit=commit, alias=alias))
310 310
311 311 nodes = dirnodes + filenodes
312 312 for node in nodes:
313 313 if node.path not in self.nodes:
314 314 self.nodes[node.path] = node
315 315 nodes.sort()
316 316
317 317 return nodes
318 318
319 319 def get_node(self, path, pre_load=None):
320 320 """
321 321 Returns `Node` object from the given `path`. If there is no node at
322 322 the given `path`, `NodeDoesNotExistError` would be raised.
323 323 """
324 324 path = self._fix_path(path)
325 325
326 326 if path not in self.nodes:
327 327 if path in self._file_paths:
328 328 node = FileNode(path, commit=self, pre_load=pre_load)
329 329 elif path in self._dir_paths:
330 330 if path == '':
331 331 node = RootNode(commit=self)
332 332 else:
333 333 node = DirNode(path, commit=self)
334 334 else:
335 335 raise self.no_node_at_path(path)
336 336
337 337 # cache node
338 338 self.nodes[path] = node
339 339 return self.nodes[path]
340 340
341 341 def get_largefile_node(self, path):
342 342 pointer_spec = self._remote.is_large_file(self.raw_id, path)
343 343 if pointer_spec:
344 344 # content of that file regular FileNode is the hash of largefile
345 345 file_id = self.get_file_content(path).strip()
346 346
347 347 if self._remote.in_largefiles_store(file_id):
348 348 lf_path = self._remote.store_path(file_id)
349 349 return LargeFileNode(lf_path, commit=self, org_path=path)
350 350 elif self._remote.in_user_cache(file_id):
351 351 lf_path = self._remote.store_path(file_id)
352 352 self._remote.link(file_id, path)
353 353 return LargeFileNode(lf_path, commit=self, org_path=path)
354 354
355 355 @LazyProperty
356 356 def _submodules(self):
357 357 """
358 358 Returns a dictionary with submodule information from substate file
359 359 of hg repository.
360 360 """
361 361 return self._remote.ctx_substate(self.raw_id)
362 362
363 363 @LazyProperty
364 364 def affected_files(self):
365 365 """
366 366 Gets a fast accessible file changes for given commit
367 367 """
368 368 return self._remote.ctx_files(self.raw_id)
369 369
370 370 @property
371 371 def added(self):
372 372 """
373 373 Returns list of added ``FileNode`` objects.
374 374 """
375 375 return AddedFileNodesGenerator([n for n in self.status[1]], self)
376 376
377 377 @property
378 378 def changed(self):
379 379 """
380 380 Returns list of modified ``FileNode`` objects.
381 381 """
382 382 return ChangedFileNodesGenerator([n for n in self.status[0]], self)
383 383
384 384 @property
385 385 def removed(self):
386 386 """
387 387 Returns list of removed ``FileNode`` objects.
388 388 """
389 389 return RemovedFileNodesGenerator([n for n in self.status[2]], self)
General Comments 0
You need to be logged in to leave comments. Login now