##// END OF EJS Templates
mercurial: show phases status in changelog....
marcink -
r1674:74692701 default
parent child Browse files
Show More
@@ -1,362 +1,373 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2014-2017 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 = repository._sanitize_commit_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.idx, 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 self.__dict__[attr] = value
85 85
86 86 @LazyProperty
87 87 def tags(self):
88 88 tags = [name for name, commit_id in self.repository.tags.iteritems()
89 89 if commit_id == self.raw_id]
90 90 return tags
91 91
92 92 @LazyProperty
93 93 def branch(self):
94 94 return safe_unicode(self._remote.ctx_branch(self.idx))
95 95
96 96 @LazyProperty
97 97 def bookmarks(self):
98 98 bookmarks = [
99 99 name for name, commit_id in self.repository.bookmarks.iteritems()
100 100 if commit_id == self.raw_id]
101 101 return bookmarks
102 102
103 103 @LazyProperty
104 104 def message(self):
105 105 return safe_unicode(self._remote.ctx_description(self.idx))
106 106
107 107 @LazyProperty
108 108 def committer(self):
109 109 return safe_unicode(self.author)
110 110
111 111 @LazyProperty
112 112 def author(self):
113 113 return safe_unicode(self._remote.ctx_user(self.idx))
114 114
115 115 @LazyProperty
116 116 def date(self):
117 117 return utcdate_fromtimestamp(*self._remote.ctx_date(self.idx))
118 118
119 119 @LazyProperty
120 120 def status(self):
121 121 """
122 122 Returns modified, added, removed, deleted files for current commit
123 123 """
124 124 return self._remote.ctx_status(self.idx)
125 125
126 126 @LazyProperty
127 127 def _file_paths(self):
128 128 return self._remote.ctx_list(self.idx)
129 129
130 130 @LazyProperty
131 131 def _dir_paths(self):
132 132 p = list(set(get_dirs_for_path(*self._file_paths)))
133 133 p.insert(0, '')
134 134 return p
135 135
136 136 @LazyProperty
137 137 def _paths(self):
138 138 return self._dir_paths + self._file_paths
139 139
140 140 @LazyProperty
141 141 def id(self):
142 142 if self.last:
143 143 return u'tip'
144 144 return self.short_id
145 145
146 146 @LazyProperty
147 147 def short_id(self):
148 148 return self.raw_id[:12]
149 149
150 150 def _make_commits(self, indexes):
151 151 return [self.repository.get_commit(commit_idx=idx)
152 152 for idx in indexes if idx >= 0]
153 153
154 154 @LazyProperty
155 155 def parents(self):
156 156 """
157 157 Returns list of parent commits.
158 158 """
159 159 parents = self._remote.ctx_parents(self.idx)
160 160 return self._make_commits(parents)
161 161
162 162 @LazyProperty
163 def phase(self):
164 phase_id = self._remote.ctx_phase(self.idx)
165 phase_text = {
166 0: 'public',
167 1: 'draft',
168 2: 'secret',
169 }.get(phase_id) or ''
170
171 return safe_unicode(phase_text)
172
173 @LazyProperty
163 174 def children(self):
164 175 """
165 176 Returns list of child commits.
166 177 """
167 178 children = self._remote.ctx_children(self.idx)
168 179 return self._make_commits(children)
169 180
170 181 def diff(self, ignore_whitespace=True, context=3):
171 182 result = self._remote.ctx_diff(
172 183 self.idx,
173 184 git=True, ignore_whitespace=ignore_whitespace, context=context)
174 185 diff = ''.join(result)
175 186 return MercurialDiff(diff)
176 187
177 188 def _fix_path(self, path):
178 189 """
179 190 Mercurial keeps filenodes as str so we need to encode from unicode
180 191 to str.
181 192 """
182 193 return safe_str(super(MercurialCommit, self)._fix_path(path))
183 194
184 195 def _get_kind(self, path):
185 196 path = self._fix_path(path)
186 197 if path in self._file_paths:
187 198 return NodeKind.FILE
188 199 elif path in self._dir_paths:
189 200 return NodeKind.DIR
190 201 else:
191 202 raise CommitError(
192 203 "Node does not exist at the given path '%s'" % (path, ))
193 204
194 205 def _get_filectx(self, path):
195 206 path = self._fix_path(path)
196 207 if self._get_kind(path) != NodeKind.FILE:
197 208 raise CommitError(
198 209 "File does not exist for idx %s at '%s'" % (self.raw_id, path))
199 210 return path
200 211
201 212 def get_file_mode(self, path):
202 213 """
203 214 Returns stat mode of the file at the given ``path``.
204 215 """
205 216 path = self._get_filectx(path)
206 217 if 'x' in self._remote.fctx_flags(self.idx, path):
207 218 return base.FILEMODE_EXECUTABLE
208 219 else:
209 220 return base.FILEMODE_DEFAULT
210 221
211 222 def is_link(self, path):
212 223 path = self._get_filectx(path)
213 224 return 'l' in self._remote.fctx_flags(self.idx, path)
214 225
215 226 def get_file_content(self, path):
216 227 """
217 228 Returns content of the file at given ``path``.
218 229 """
219 230 path = self._get_filectx(path)
220 231 return self._remote.fctx_data(self.idx, path)
221 232
222 233 def get_file_size(self, path):
223 234 """
224 235 Returns size of the file at given ``path``.
225 236 """
226 237 path = self._get_filectx(path)
227 238 return self._remote.fctx_size(self.idx, path)
228 239
229 240 def get_file_history(self, path, limit=None, pre_load=None):
230 241 """
231 242 Returns history of file as reversed list of `MercurialCommit` objects
232 243 for which file at given ``path`` has been modified.
233 244 """
234 245 path = self._get_filectx(path)
235 246 hist = self._remote.file_history(self.idx, path, limit)
236 247 return [
237 248 self.repository.get_commit(commit_id=commit_id, pre_load=pre_load)
238 249 for commit_id in hist]
239 250
240 251 def get_file_annotate(self, path, pre_load=None):
241 252 """
242 253 Returns a generator of four element tuples with
243 254 lineno, commit_id, commit lazy loader and line
244 255 """
245 256 result = self._remote.fctx_annotate(self.idx, path)
246 257
247 258 for ln_no, commit_id, content in result:
248 259 yield (
249 260 ln_no, commit_id,
250 261 lambda: self.repository.get_commit(commit_id=commit_id,
251 262 pre_load=pre_load),
252 263 content)
253 264
254 265 def get_nodes(self, path):
255 266 """
256 267 Returns combined ``DirNode`` and ``FileNode`` objects list representing
257 268 state of commit at the given ``path``. If node at the given ``path``
258 269 is not instance of ``DirNode``, CommitError would be raised.
259 270 """
260 271
261 272 if self._get_kind(path) != NodeKind.DIR:
262 273 raise CommitError(
263 274 "Directory does not exist for idx %s at '%s'" %
264 275 (self.idx, path))
265 276 path = self._fix_path(path)
266 277
267 278 filenodes = [
268 279 FileNode(f, commit=self) for f in self._file_paths
269 280 if os.path.dirname(f) == path]
270 281 # TODO: johbo: Check if this can be done in a more obvious way
271 282 dirs = path == '' and '' or [
272 283 d for d in self._dir_paths
273 284 if d and vcspath.dirname(d) == path]
274 285 dirnodes = [
275 286 DirNode(d, commit=self) for d in dirs
276 287 if os.path.dirname(d) == path]
277 288
278 289 alias = self.repository.alias
279 290 for k, vals in self._submodules.iteritems():
280 291 loc = vals[0]
281 292 commit = vals[1]
282 293 dirnodes.append(
283 294 SubModuleNode(k, url=loc, commit=commit, alias=alias))
284 295 nodes = dirnodes + filenodes
285 296 # cache nodes
286 297 for node in nodes:
287 298 self.nodes[node.path] = node
288 299 nodes.sort()
289 300
290 301 return nodes
291 302
292 303 def get_node(self, path, pre_load=None):
293 304 """
294 305 Returns `Node` object from the given `path`. If there is no node at
295 306 the given `path`, `NodeDoesNotExistError` would be raised.
296 307 """
297 308 path = self._fix_path(path)
298 309
299 310 if path not in self.nodes:
300 311 if path in self._file_paths:
301 312 node = FileNode(path, commit=self, pre_load=pre_load)
302 313 elif path in self._dir_paths:
303 314 if path == '':
304 315 node = RootNode(commit=self)
305 316 else:
306 317 node = DirNode(path, commit=self)
307 318 else:
308 319 raise self.no_node_at_path(path)
309 320
310 321 # cache node
311 322 self.nodes[path] = node
312 323 return self.nodes[path]
313 324
314 325 def get_largefile_node(self, path):
315 326
316 327 if self._remote.is_large_file(path):
317 328 # content of that file regular FileNode is the hash of largefile
318 329 file_id = self.get_file_content(path).strip()
319 330
320 331 if self._remote.in_largefiles_store(file_id):
321 332 lf_path = self._remote.store_path(file_id)
322 333 return LargeFileNode(lf_path, commit=self, org_path=path)
323 334 elif self._remote.in_user_cache(file_id):
324 335 lf_path = self._remote.store_path(file_id)
325 336 self._remote.link(file_id, path)
326 337 return LargeFileNode(lf_path, commit=self, org_path=path)
327 338
328 339 @LazyProperty
329 340 def _submodules(self):
330 341 """
331 342 Returns a dictionary with submodule information from substate file
332 343 of hg repository.
333 344 """
334 345 return self._remote.ctx_substate(self.idx)
335 346
336 347 @LazyProperty
337 348 def affected_files(self):
338 349 """
339 350 Gets a fast accessible file changes for given commit
340 351 """
341 352 return self._remote.ctx_files(self.idx)
342 353
343 354 @property
344 355 def added(self):
345 356 """
346 357 Returns list of added ``FileNode`` objects.
347 358 """
348 359 return AddedFileNodesGenerator([n for n in self.status[1]], self)
349 360
350 361 @property
351 362 def changed(self):
352 363 """
353 364 Returns list of modified ``FileNode`` objects.
354 365 """
355 366 return ChangedFileNodesGenerator([n for n in self.status[0]], self)
356 367
357 368 @property
358 369 def removed(self):
359 370 """
360 371 Returns list of removed ``FileNode`` objects.
361 372 """
362 373 return RemovedFileNodesGenerator([n for n in self.status[2]], self)
@@ -1,101 +1,109 b''
1 1 // tags.less
2 2 // For use in RhodeCode applications;
3 3 // see style guide documentation for guidelines.
4 4
5 5 // TAGS
6 6 .tag,
7 7 .tagtag {
8 8 display: inline-block;
9 9 min-height: 0;
10 10 margin: 0 auto;
11 11 padding: .25em;
12 12 text-align: center;
13 13 font-size: (-1 + @basefontsize); //fit in tables
14 14 line-height: .9em;
15 15 border: none;
16 16 .border-radius(@border-radius);
17 17 font-family: @text-regular;
18 18 background-image: none;
19 19 color: @grey4;
20 20 .border ( @border-thickness-tags, @grey4 );
21 21 white-space: nowrap;
22 22 a {
23 23 color: inherit;
24 24 text-decoration: underline;
25 25
26 26 i,
27 27 [class^="icon-"]:before,
28 28 [class*=" icon-"]:before {
29 29 text-decoration: none;
30 30 }
31 31 }
32 32 }
33 33
34 34 .tag0 { .border ( @border-thickness-tags, @grey4 ); color:@grey4; }
35 35 .tag1 { .border ( @border-thickness-tags, @color1 ); color:@color1; }
36 36 .tag2 { .border ( @border-thickness-tags, @color2 ); color:@color2; }
37 37 .tag3 { .border ( @border-thickness-tags, @color3 ); color:@color3; }
38 38 .tag4 { .border ( @border-thickness-tags, @color4 ); color:@color4; }
39 39 .tag5 { .border ( @border-thickness-tags, @color5 ); color:@color5; }
40 40 .tag6 { .border ( @border-thickness-tags, @color6 ); color:@color6; }
41 41 .tag7 { .border ( @border-thickness-tags, @color7 ); color:@color7; }
42 42 .tag8 { .border ( @border-thickness-tags, @color8 ); color:@color8; }
43 43
44 44 .metatag-list {
45 45 margin: 0;
46 46 padding: 0;
47 47
48 48 li {
49 49 margin: 0 0 @padding;
50 50 line-height: 1em;
51 51 list-style-type: none;
52 52
53 53 &:before { content: none; }
54 54 }
55 55 }
56 56
57 57 .branchtag, .booktag {
58 58 &:extend(.tag);
59 59
60 60
61 61 a {
62 62 color:inherit;
63 63 }
64 64 }
65 65
66 66 .metatag {
67 67 &:extend(.tag);
68 68 a {
69 69 color:inherit;
70 70 text-decoration: underline;
71 71 }
72 72 }
73 73
74 74 [tag="featured"] { &:extend(.tag1); }
75 75 [tag="stale"] { &:extend(.tag2); }
76 76 [tag="dead"] { &:extend(.tag3); }
77 77 [tag="lang"] { &:extend(.tag4); }
78 78 [tag="license"] { &:extend(.tag5); }
79 79 [tag="requires"] { &:extend(.tag6); }
80 80 [tag="recommends"] { &:extend(.tag7); }
81 81 [tag="see"] { &:extend(.tag8); }
82 82
83 83 .perm_overriden {
84 84 text-decoration: line-through;
85 85 opacity: 0.6;
86 86 }
87 87
88 88 .perm_tag {
89 89 &:extend(.tag);
90 90
91 91 &.read {
92 92 &:extend(.tag1);
93 93 }
94 94
95 95 &.write {
96 96 &:extend(.tag4);
97 97 }
98 98 &.admin {
99 99 &:extend(.tag5);
100 100 }
101 } No newline at end of file
101 }
102
103 .phase-draft {
104 color: @color3
105 }
106
107 .phase-secret {
108 color:@grey3
109 }
@@ -1,122 +1,127 b''
1 1 ## small box that displays changed/added/removed details fetched by AJAX
2 2 <%namespace name="base" file="/base/base.mako"/>
3 3
4 4
5 5 % if c.prev_page:
6 6 <tr>
7 7 <td colspan="9" class="load-more-commits">
8 8 <a class="prev-commits" href="#loadPrevCommits" onclick="commitsController.loadPrev(this, ${c.prev_page}, '${c.branch_name}');return false">
9 9 ${_('load previous')}
10 10 </a>
11 11 </td>
12 12 </tr>
13 13 % endif
14 14
15 15 % for cnt,commit in enumerate(c.pagination):
16 16 <tr id="sha_${commit.raw_id}" class="changelogRow container ${'tablerow%s' % (cnt%2)}">
17 17
18 18 <td class="td-checkbox">
19 19 ${h.checkbox(commit.raw_id,class_="commit-range")}
20 20 </td>
21 21 <td class="td-status">
22 22
23 23 %if c.statuses.get(commit.raw_id):
24 24 <div class="changeset-status-ico">
25 25 %if c.statuses.get(commit.raw_id)[2]:
26 26 <a class="tooltip" title="${_('Commit status: %s\nClick to open associated pull request #%s') % (h.commit_status_lbl(c.statuses.get(commit.raw_id)[0]), c.statuses.get(commit.raw_id)[2])}" href="${h.url('pullrequest_show',repo_name=c.statuses.get(commit.raw_id)[3],pull_request_id=c.statuses.get(commit.raw_id)[2])}">
27 27 <div class="${'flag_status %s' % c.statuses.get(commit.raw_id)[0]}"></div>
28 28 </a>
29 29 %else:
30 30 <a class="tooltip" title="${_('Commit status: %s') % h.commit_status_lbl(c.statuses.get(commit.raw_id)[0])}" href="${h.url('changeset_home',repo_name=c.repo_name,revision=commit.raw_id,anchor='comment-%s' % c.comments[commit.raw_id][0].comment_id)}">
31 31 <div class="${'flag_status %s' % c.statuses.get(commit.raw_id)[0]}"></div>
32 32 </a>
33 33 %endif
34 34 </div>
35 35 %else:
36 36 <div class="tooltip flag_status not_reviewed" title="${_('Commit status: Not Reviewed')}"></div>
37 37 %endif
38 38 </td>
39 39 <td class="td-comments comments-col">
40 40 %if c.comments.get(commit.raw_id):
41 41 <a title="${_('Commit has comments')}" href="${h.url('changeset_home',repo_name=c.repo_name,revision=commit.raw_id,anchor='comment-%s' % c.comments[commit.raw_id][0].comment_id)}">
42 42 <i class="icon-comment"></i> ${len(c.comments[commit.raw_id])}
43 43 </a>
44 44 %endif
45 45 </td>
46 46 <td class="td-hash">
47 47 <code>
48 48 <a href="${h.url('changeset_home',repo_name=c.repo_name,revision=commit.raw_id)}">
49 49 <span class="commit_hash">${h.show_id(commit)}</span>
50 50 </a>
51 % if hasattr(commit, 'phase'):
52 % if commit.phase != 'public':
53 <span class="tag phase-${commit.phase} tooltip" title="${_('commit phase')}">${commit.phase}</span>
54 % endif
55 % endif
51 56 </code>
52 57 </td>
53 58 <td class="td-message expand_commit" data-commit-id="${commit.raw_id}" title="${_('Expand commit message')}" onclick="commitsController.expandCommit(this); return false">
54 59 <div class="show_more_col">
55 60 <i class="show_more"></i>&nbsp;
56 61 </div>
57 62 </td>
58 63 <td class="td-description mid">
59 64 <div class="log-container truncate-wrap">
60 65 <div class="message truncate" id="c-${commit.raw_id}">${h.urlify_commit_message(commit.message, c.repo_name)}</div>
61 66 </div>
62 67 </td>
63 68
64 69 <td class="td-time">
65 70 ${h.age_component(commit.date)}
66 71 </td>
67 72 <td class="td-user">
68 73 ${base.gravatar_with_user(commit.author)}
69 74 </td>
70 75
71 76 <td class="td-tags tags-col">
72 77 <div id="t-${commit.raw_id}">
73 78
74 79 ## merge
75 80 %if commit.merge:
76 81 <span class="tag mergetag">
77 82 <i class="icon-merge"></i>${_('merge')}
78 83 </span>
79 84 %endif
80 85
81 86 ## branch
82 87 %if commit.branch:
83 88 <span class="tag branchtag" title="${_('Branch %s') % commit.branch}">
84 89 <a href="${h.url('changelog_home',repo_name=c.repo_name,branch=commit.branch)}"><i class="icon-code-fork"></i>${h.shorter(commit.branch)}</a>
85 90 </span>
86 91 %endif
87 92
88 93 ## bookmarks
89 94 %if h.is_hg(c.rhodecode_repo):
90 95 %for book in commit.bookmarks:
91 96 <span class="tag booktag" title="${_('Bookmark %s') % book}">
92 97 <a href="${h.url('files_home',repo_name=c.repo_name,revision=commit.raw_id)}"><i class="icon-bookmark"></i>${h.shorter(book)}</a>
93 98 </span>
94 99 %endfor
95 100 %endif
96 101
97 102 ## tags
98 103 %for tag in commit.tags:
99 104 <span class="tag tagtag" title="${_('Tag %s') % tag}">
100 105 <a href="${h.url('files_home',repo_name=c.repo_name,revision=commit.raw_id)}"><i class="icon-tag"></i>${h.shorter(tag)}</a>
101 106 </span>
102 107 %endfor
103 108
104 109 </div>
105 110 </td>
106 111 </tr>
107 112 % endfor
108 113
109 114 % if c.next_page:
110 115 <tr>
111 116 <td colspan="9" class="load-more-commits">
112 117 <a class="next-commits" href="#loadNextCommits" onclick="commitsController.loadNext(this, ${c.next_page}, '${c.branch_name}');return false">
113 118 ${_('load next')}
114 119 </a>
115 120 </td>
116 121 </tr>
117 122 % endif
118 123 <tr class="chunk-graph-data" style="display:none"
119 124 data-graph='${c.graph_data|n}'
120 125 data-node='${c.prev_page}:${c.next_page}'
121 126 data-commits='${c.graph_commits|n}'>
122 127 </tr> No newline at end of file
General Comments 0
You need to be logged in to leave comments. Login now