##// END OF EJS Templates
svn: date-parser should use UTC dates to be consistent with other backends.
marcink -
r1348:8875ec9d default
parent child Browse files
Show More
@@ -1,234 +1,236 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 SVN commit module
23 23 """
24 24
25 25
26 26 import dateutil.parser
27 27 from zope.cachedescriptors.property import Lazy as LazyProperty
28 28
29 29 from rhodecode.lib.utils import safe_str, safe_unicode
30 30 from rhodecode.lib.vcs import nodes, path as vcspath
31 31 from rhodecode.lib.vcs.backends import base
32 32 from rhodecode.lib.vcs.exceptions import CommitError, NodeDoesNotExistError
33 33
34 34
35 35 _SVN_PROP_TRUE = '*'
36 36
37 37
38 38 class SubversionCommit(base.BaseCommit):
39 39 """
40 40 Subversion specific implementation of commits
41 41
42 42 .. attribute:: branch
43 43
44 44 The Subversion backend does not support to assign branches to
45 45 specific commits. This attribute has always the value `None`.
46 46
47 47 """
48 48
49 49 def __init__(self, repository, commit_id):
50 50 self.repository = repository
51 51 self.idx = self.repository._get_commit_idx(commit_id)
52 52 self._svn_rev = self.idx + 1
53 53 self._remote = repository._remote
54 54 # TODO: handling of raw_id should be a method on repository itself,
55 55 # which knows how to translate commit index and commit id
56 56 self.raw_id = commit_id
57 57 self.short_id = commit_id
58 58 self.id = 'r%s' % (commit_id, )
59 59
60 60 # TODO: Implement the following placeholder attributes
61 61 self.nodes = {}
62 62 self.tags = []
63 63
64 64 @property
65 65 def author(self):
66 66 return safe_unicode(self._properties.get('svn:author'))
67 67
68 68 @property
69 69 def date(self):
70 70 return _date_from_svn_properties(self._properties)
71 71
72 72 @property
73 73 def message(self):
74 74 return safe_unicode(self._properties.get('svn:log'))
75 75
76 76 @LazyProperty
77 77 def _properties(self):
78 78 return self._remote.revision_properties(self._svn_rev)
79 79
80 80 @LazyProperty
81 81 def parents(self):
82 82 parent_idx = self.idx - 1
83 83 if parent_idx >= 0:
84 84 parent = self.repository.get_commit(commit_idx=parent_idx)
85 85 return [parent]
86 86 return []
87 87
88 88 @LazyProperty
89 89 def children(self):
90 90 child_idx = self.idx + 1
91 91 if child_idx < len(self.repository.commit_ids):
92 92 child = self.repository.get_commit(commit_idx=child_idx)
93 93 return [child]
94 94 return []
95 95
96 96 def get_file_mode(self, path):
97 97 # Note: Subversion flags files which are executable with a special
98 98 # property `svn:executable` which is set to the value ``"*"``.
99 99 if self._get_file_property(path, 'svn:executable') == _SVN_PROP_TRUE:
100 100 return base.FILEMODE_EXECUTABLE
101 101 else:
102 102 return base.FILEMODE_DEFAULT
103 103
104 104 def is_link(self, path):
105 105 # Note: Subversion has a flag for special files, the content of the
106 106 # file contains the type of that file.
107 107 if self._get_file_property(path, 'svn:special') == _SVN_PROP_TRUE:
108 108 return self.get_file_content(path).startswith('link')
109 109 return False
110 110
111 111 def _get_file_property(self, path, name):
112 112 file_properties = self._remote.node_properties(
113 113 safe_str(path), self._svn_rev)
114 114 return file_properties.get(name)
115 115
116 116 def get_file_content(self, path):
117 117 path = self._fix_path(path)
118 118 return self._remote.get_file_content(safe_str(path), self._svn_rev)
119 119
120 120 def get_file_size(self, path):
121 121 path = self._fix_path(path)
122 122 return self._remote.get_file_size(safe_str(path), self._svn_rev)
123 123
124 124 def get_file_history(self, path, limit=None, pre_load=None):
125 125 path = safe_str(self._fix_path(path))
126 126 history = self._remote.node_history(path, self._svn_rev, limit)
127 127 return [
128 128 self.repository.get_commit(commit_id=str(svn_rev))
129 129 for svn_rev in history]
130 130
131 131 def get_file_annotate(self, path, pre_load=None):
132 132 result = self._remote.file_annotate(safe_str(path), self._svn_rev)
133 133
134 134 for zero_based_line_no, svn_rev, content in result:
135 135 commit_id = str(svn_rev)
136 136 line_no = zero_based_line_no + 1
137 137 yield (
138 138 line_no,
139 139 commit_id,
140 140 lambda: self.repository.get_commit(commit_id=commit_id),
141 141 content)
142 142
143 143 def get_node(self, path):
144 144 path = self._fix_path(path)
145 145 if path not in self.nodes:
146 146
147 147 if path == '':
148 148 node = nodes.RootNode(commit=self)
149 149 else:
150 150 node_type = self._remote.get_node_type(
151 151 safe_str(path), self._svn_rev)
152 152 if node_type == 'dir':
153 153 node = nodes.DirNode(path, commit=self)
154 154 elif node_type == 'file':
155 155 node = nodes.FileNode(path, commit=self)
156 156 else:
157 157 raise NodeDoesNotExistError(self.no_node_at_path(path))
158 158
159 159 self.nodes[path] = node
160 160 return self.nodes[path]
161 161
162 162 def get_nodes(self, path):
163 163 if self._get_kind(path) != nodes.NodeKind.DIR:
164 164 raise CommitError(
165 165 "Directory does not exist for commit %s at "
166 166 " '%s'" % (self.raw_id, path))
167 167 path = self._fix_path(path)
168 168
169 169 path_nodes = []
170 170 for name, kind in self._remote.get_nodes(
171 171 safe_str(path), revision=self._svn_rev):
172 172 node_path = vcspath.join(path, name)
173 173 if kind == 'dir':
174 174 node = nodes.DirNode(node_path, commit=self)
175 175 elif kind == 'file':
176 176 node = nodes.FileNode(node_path, commit=self)
177 177 else:
178 178 raise ValueError("Node kind %s not supported." % (kind, ))
179 179 self.nodes[node_path] = node
180 180 path_nodes.append(node)
181 181
182 182 return path_nodes
183 183
184 184 def _get_kind(self, path):
185 185 path = self._fix_path(path)
186 186 kind = self._remote.get_node_type(path, self._svn_rev)
187 187 if kind == 'file':
188 188 return nodes.NodeKind.FILE
189 189 elif kind == 'dir':
190 190 return nodes.NodeKind.DIR
191 191 else:
192 192 raise CommitError(
193 193 "Node does not exist at the given path '%s'" % (path, ))
194 194
195 195 @LazyProperty
196 196 def _changes_cache(self):
197 197 return self._remote.revision_changes(self._svn_rev)
198 198
199 199 @LazyProperty
200 200 def affected_files(self):
201 201 changed_files = set()
202 202 for files in self._changes_cache.itervalues():
203 203 changed_files.update(files)
204 204 return list(changed_files)
205 205
206 206 @LazyProperty
207 207 def id(self):
208 208 return self.raw_id
209 209
210 210 @property
211 211 def added(self):
212 212 return nodes.AddedFileNodesGenerator(
213 213 self._changes_cache['added'], self)
214 214
215 215 @property
216 216 def changed(self):
217 217 return nodes.ChangedFileNodesGenerator(
218 218 self._changes_cache['changed'], self)
219 219
220 220 @property
221 221 def removed(self):
222 222 return nodes.RemovedFileNodesGenerator(
223 223 self._changes_cache['removed'], self)
224 224
225 225
226 226 def _date_from_svn_properties(properties):
227 227 """
228 228 Parses the date out of given svn properties.
229 229
230 230 :return: :class:`datetime.datetime` instance. The object is naive.
231 231 """
232
232 233 aware_date = dateutil.parser.parse(properties.get('svn:date'))
233 local_date = aware_date.astimezone(dateutil.tz.tzlocal())
234 return local_date.replace(tzinfo=None)
234 # final_date = aware_date.astimezone(dateutil.tz.tzlocal())
235 final_date = aware_date
236 return final_date.replace(tzinfo=None)
@@ -1,302 +1,301 b''
1 1 # -*- coding: utf-8 -*-
2 2
3 3 # Copyright (C) 2010-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 import pytest
22 22
23 23 from rhodecode.lib.helpers import _shorten_commit_id
24 24 from rhodecode.tests import url
25 25
26 26
27 27 @pytest.mark.usefixtures("app")
28 28 class TestChangesetController(object):
29 29
30 30 def test_index(self, backend):
31 31 commit_id = self.commit_id[backend.alias]
32 32 response = self.app.get(url(
33 33 controller='changeset', action='index',
34 34 repo_name=backend.repo_name, revision=commit_id))
35 35 response.mustcontain('Added a symlink')
36 36 response.mustcontain(commit_id)
37 37 response.mustcontain('No newline at end of file')
38 38
39 39 def test_index_raw(self, backend):
40 40 commit_id = self.commit_id[backend.alias]
41 41 response = self.app.get(url(
42 42 controller='changeset', action='changeset_raw',
43 43 repo_name=backend.repo_name, revision=commit_id))
44 44 assert response.body == self.diffs[backend.alias]
45 45
46 46 def test_index_raw_patch(self, backend):
47 47 response = self.app.get(url(
48 48 controller='changeset', action='changeset_patch',
49 49 repo_name=backend.repo_name,
50 50 revision=self.commit_id[backend.alias]))
51 51 assert response.body == self.patches[backend.alias]
52 52
53 53 def test_index_changeset_download(self, backend):
54 54 response = self.app.get(url(
55 55 controller='changeset', action='changeset_download',
56 56 repo_name=backend.repo_name,
57 57 revision=self.commit_id[backend.alias]))
58 58 assert response.body == self.diffs[backend.alias]
59 59
60 60 def test_single_commit_page_different_ops(self, backend):
61 61 commit_id = {
62 62 'hg': '603d6c72c46d953420c89d36372f08d9f305f5dd',
63 63 'git': '03fa803d7e9fb14daa9a3089e0d1494eda75d986',
64 64 'svn': '337',
65 65 }
66 66 commit_id = commit_id[backend.alias]
67 67 response = self.app.get(url(
68 68 controller='changeset', action='index',
69 69 repo_name=backend.repo_name, revision=commit_id))
70 70
71 71 response.mustcontain(_shorten_commit_id(commit_id))
72 72 response.mustcontain('21 files changed: 943 inserted, 288 deleted')
73 73
74 74 # files op files
75 75 response.mustcontain('File no longer present at commit: %s' %
76 76 _shorten_commit_id(commit_id))
77 77
78 78 # svn uses a different filename
79 79 if backend.alias == 'svn':
80 80 response.mustcontain('new file 10644')
81 81 else:
82 82 response.mustcontain('new file 100644')
83 83 response.mustcontain('Changed theme to ADC theme') # commit msg
84 84
85 85 self._check_new_diff_menus(response, right_menu=True)
86 86
87 87 def test_commit_range_page_different_ops(self, backend):
88 88 commit_id_range = {
89 89 'hg': (
90 90 '25d7e49c18b159446cadfa506a5cf8ad1cb04067',
91 91 '603d6c72c46d953420c89d36372f08d9f305f5dd'),
92 92 'git': (
93 93 '6fc9270775aaf5544c1deb014f4ddd60c952fcbb',
94 94 '03fa803d7e9fb14daa9a3089e0d1494eda75d986'),
95 95 'svn': (
96 96 '335',
97 97 '337'),
98 98 }
99 99 commit_ids = commit_id_range[backend.alias]
100 100 commit_id = '%s...%s' % (commit_ids[0], commit_ids[1])
101 101 response = self.app.get(url(
102 102 controller='changeset', action='index',
103 103 repo_name=backend.repo_name, revision=commit_id))
104 104
105 105 response.mustcontain(_shorten_commit_id(commit_ids[0]))
106 106 response.mustcontain(_shorten_commit_id(commit_ids[1]))
107 107
108 108 # svn is special
109 109 if backend.alias == 'svn':
110 110 response.mustcontain('new file 10644')
111 111 response.mustcontain('1 file changed: 5 inserted, 1 deleted')
112 112 response.mustcontain('12 files changed: 236 inserted, 22 deleted')
113 113 response.mustcontain('21 files changed: 943 inserted, 288 deleted')
114 114 else:
115 115 response.mustcontain('new file 100644')
116 116 response.mustcontain('12 files changed: 222 inserted, 20 deleted')
117 117 response.mustcontain('21 files changed: 943 inserted, 288 deleted')
118 118
119 119 # files op files
120 120 response.mustcontain('File no longer present at commit: %s' %
121 121 _shorten_commit_id(commit_ids[1]))
122 122 response.mustcontain('Added docstrings to vcs.cli') # commit msg
123 123 response.mustcontain('Changed theme to ADC theme') # commit msg
124 124
125 125 self._check_new_diff_menus(response)
126 126
127 127 def test_combined_compare_commit_page_different_ops(self, backend):
128 128 commit_id_range = {
129 129 'hg': (
130 130 '4fdd71e9427417b2e904e0464c634fdee85ec5a7',
131 131 '603d6c72c46d953420c89d36372f08d9f305f5dd'),
132 132 'git': (
133 133 'f5fbf9cfd5f1f1be146f6d3b38bcd791a7480c13',
134 134 '03fa803d7e9fb14daa9a3089e0d1494eda75d986'),
135 135 'svn': (
136 136 '335',
137 137 '337'),
138 138 }
139 139 commit_ids = commit_id_range[backend.alias]
140 140 response = self.app.get(url(
141 141 controller='compare', action='compare',
142 142 repo_name=backend.repo_name,
143 143 source_ref_type='rev', source_ref=commit_ids[0],
144 144 target_ref_type='rev', target_ref=commit_ids[1], ))
145 145
146 146 response.mustcontain(_shorten_commit_id(commit_ids[0]))
147 147 response.mustcontain(_shorten_commit_id(commit_ids[1]))
148 148
149 149 # files op files
150 150 response.mustcontain('File no longer present at commit: %s' %
151 151 _shorten_commit_id(commit_ids[1]))
152 152
153 153 # svn is special
154 154 if backend.alias == 'svn':
155 155 response.mustcontain('new file 10644')
156 156 response.mustcontain('32 files changed: 1179 inserted, 310 deleted')
157 157 else:
158 158 response.mustcontain('new file 100644')
159 159 response.mustcontain('32 files changed: 1165 inserted, 308 deleted')
160 160
161 161 response.mustcontain('Added docstrings to vcs.cli') # commit msg
162 162 response.mustcontain('Changed theme to ADC theme') # commit msg
163 163
164 164 self._check_new_diff_menus(response)
165 165
166 166 def test_changeset_range(self, backend):
167 167 self._check_changeset_range(
168 168 backend, self.commit_id_range, self.commit_id_range_result)
169 169
170 170 def test_changeset_range_with_initial_commit(self, backend):
171 171 commit_id_range = {
172 172 'hg': (
173 173 'b986218ba1c9b0d6a259fac9b050b1724ed8e545'
174 174 '...6cba7170863a2411822803fa77a0a264f1310b35'),
175 175 'git': (
176 176 'c1214f7e79e02fc37156ff215cd71275450cffc3'
177 177 '...fa6600f6848800641328adbf7811fd2372c02ab2'),
178 178 'svn': '1...3',
179 179 }
180 180 commit_id_range_result = {
181 181 'hg': ['b986218ba1c9', '3d8f361e72ab', '6cba7170863a'],
182 182 'git': ['c1214f7e79e0', '38b5fe81f109', 'fa6600f68488'],
183 183 'svn': ['1', '2', '3'],
184 184 }
185 185 self._check_changeset_range(
186 186 backend, commit_id_range, commit_id_range_result)
187 187
188 188 def _check_changeset_range(
189 189 self, backend, commit_id_ranges, commit_id_range_result):
190 190 response = self.app.get(
191 191 url(controller='changeset', action='index',
192 192 repo_name=backend.repo_name,
193 193 revision=commit_id_ranges[backend.alias]))
194 194 expected_result = commit_id_range_result[backend.alias]
195 195 response.mustcontain('{} commits'.format(len(expected_result)))
196 196 for commit_id in expected_result:
197 197 response.mustcontain(commit_id)
198 198
199 199 commit_id = {
200 200 'hg': '2062ec7beeeaf9f44a1c25c41479565040b930b2',
201 201 'svn': '393',
202 202 'git': 'fd627b9e0dd80b47be81af07c4a98518244ed2f7',
203 203 }
204 204
205 205 commit_id_range = {
206 206 'hg': (
207 207 'a53d9201d4bc278910d416d94941b7ea007ecd52'
208 208 '...2062ec7beeeaf9f44a1c25c41479565040b930b2'),
209 209 'git': (
210 210 '7ab37bc680b4aa72c34d07b230c866c28e9fc204'
211 211 '...fd627b9e0dd80b47be81af07c4a98518244ed2f7'),
212 212 'svn': '391...393',
213 213 }
214 214
215 215 commit_id_range_result = {
216 216 'hg': ['a53d9201d4bc', '96507bd11ecc', '2062ec7beeea'],
217 217 'git': ['7ab37bc680b4', '5f2c6ee19592', 'fd627b9e0dd8'],
218 218 'svn': ['391', '392', '393'],
219 219 }
220 220
221 221 diffs = {
222 222 'hg': r"""diff --git a/README b/README
223 223 new file mode 120000
224 224 --- /dev/null
225 225 +++ b/README
226 226 @@ -0,0 +1,1 @@
227 227 +README.rst
228 228 \ No newline at end of file
229 229 """,
230 230 'git': r"""diff --git a/README b/README
231 231 new file mode 120000
232 232 index 0000000000000000000000000000000000000000..92cacd285355271487b7e379dba6ca60f9a554a4
233 233 --- /dev/null
234 234 +++ b/README
235 235 @@ -0,0 +1 @@
236 236 +README.rst
237 237 \ No newline at end of file
238 238 """,
239 239 'svn': """Index: README
240 240 ===================================================================
241 241 diff --git a/README b/README
242 242 new file mode 10644
243 243 --- /dev/null\t(revision 0)
244 244 +++ b/README\t(revision 393)
245 245 @@ -0,0 +1 @@
246 246 +link README.rst
247 247 \\ No newline at end of file
248 248 """,
249 249 }
250 250
251 251 patches = {
252 252 'hg': r"""# HG changeset patch
253 253 # User Marcin Kuzminski <marcin@python-works.com>
254 254 # Date 2014-01-07 12:21:40
255 255 # Node ID 2062ec7beeeaf9f44a1c25c41479565040b930b2
256 256 # Parent 96507bd11ecc815ebc6270fdf6db110928c09c1e
257 257
258 258 Added a symlink
259 259
260 260 """ + diffs['hg'],
261 261 'git': r"""From fd627b9e0dd80b47be81af07c4a98518244ed2f7 2014-01-07 12:22:20
262 262 From: Marcin Kuzminski <marcin@python-works.com>
263 263 Date: 2014-01-07 12:22:20
264 264 Subject: [PATCH] Added a symlink
265 265
266 266 ---
267 267
268 268 """ + diffs['git'],
269 269 'svn': r"""# SVN changeset patch
270 270 # User marcin
271 271 # Date 2014-09-02 12:25:22.071142
272 272 # Revision 393
273 273
274 274 Added a symlink
275 275
276 276 """ + diffs['svn'],
277 277 }
278 278
279 279 def _check_diff_menus(self, response, right_menu=False,):
280 280 # diff menus
281 281 for elem in ['Show File', 'Unified Diff', 'Side-by-side Diff',
282 282 'Raw Diff', 'Download Diff']:
283 283 response.mustcontain(elem)
284 284
285 285 # right pane diff menus
286 286 if right_menu:
287 287 for elem in ['Ignore whitespace', 'Increase context',
288 288 'Hide comments']:
289 289 response.mustcontain(elem)
290 290
291
292 291 def _check_new_diff_menus(self, response, right_menu=False,):
293 292 # diff menus
294 293 for elem in ['Show file before', 'Show file after',
295 294 'Raw diff', 'Download diff']:
296 295 response.mustcontain(elem)
297 296
298 297 # right pane diff menus
299 298 if right_menu:
300 299 for elem in ['Ignore whitespace', 'Increase context',
301 300 'Hide comments']:
302 301 response.mustcontain(elem)
General Comments 0
You need to be logged in to leave comments. Login now