##// END OF EJS Templates
svn: fixed problem with special characters inside subdirectories.
dan -
r4338:f83fb4e5 default
parent child Browse files
Show More
@@ -1,254 +1,253 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2
2
3 # Copyright (C) 2014-2020 RhodeCode GmbH
3 # Copyright (C) 2014-2020 RhodeCode GmbH
4 #
4 #
5 # This program is free software: you can redistribute it and/or modify
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License, version 3
6 # it under the terms of the GNU Affero General Public License, version 3
7 # (only), as published by the Free Software Foundation.
7 # (only), as published by the Free Software Foundation.
8 #
8 #
9 # This program is distributed in the hope that it will be useful,
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
12 # GNU General Public License for more details.
13 #
13 #
14 # You should have received a copy of the GNU Affero General Public License
14 # You should have received a copy of the GNU Affero General Public License
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
15 # along with this program. If not, see <http://www.gnu.org/licenses/>.
16 #
16 #
17 # This program is dual-licensed. If you wish to learn more about the
17 # This program is dual-licensed. If you wish to learn more about the
18 # RhodeCode Enterprise Edition, including its added features, Support services,
18 # RhodeCode Enterprise Edition, including its added features, Support services,
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
19 # and proprietary license terms, please see https://rhodecode.com/licenses/
20
20
21 """
21 """
22 SVN commit module
22 SVN commit module
23 """
23 """
24
24
25
25
26 import dateutil.parser
26 import dateutil.parser
27 from zope.cachedescriptors.property import Lazy as LazyProperty
27 from zope.cachedescriptors.property import Lazy as LazyProperty
28
28
29 from rhodecode.lib.utils import safe_str, safe_unicode
29 from rhodecode.lib.utils import safe_str, safe_unicode
30 from rhodecode.lib.vcs import nodes, path as vcspath
30 from rhodecode.lib.vcs import nodes, path as vcspath
31 from rhodecode.lib.vcs.backends import base
31 from rhodecode.lib.vcs.backends import base
32 from rhodecode.lib.vcs.exceptions import CommitError, NodeDoesNotExistError
32 from rhodecode.lib.vcs.exceptions import CommitError, NodeDoesNotExistError
33
33
34
34
35 _SVN_PROP_TRUE = '*'
35 _SVN_PROP_TRUE = '*'
36
36
37
37
38 class SubversionCommit(base.BaseCommit):
38 class SubversionCommit(base.BaseCommit):
39 """
39 """
40 Subversion specific implementation of commits
40 Subversion specific implementation of commits
41
41
42 .. attribute:: branch
42 .. attribute:: branch
43
43
44 The Subversion backend does not support to assign branches to
44 The Subversion backend does not support to assign branches to
45 specific commits. This attribute has always the value `None`.
45 specific commits. This attribute has always the value `None`.
46
46
47 """
47 """
48
48
49 def __init__(self, repository, commit_id):
49 def __init__(self, repository, commit_id):
50 self.repository = repository
50 self.repository = repository
51 self.idx = self.repository._get_commit_idx(commit_id)
51 self.idx = self.repository._get_commit_idx(commit_id)
52 self._svn_rev = self.idx + 1
52 self._svn_rev = self.idx + 1
53 self._remote = repository._remote
53 self._remote = repository._remote
54 # TODO: handling of raw_id should be a method on repository itself,
54 # TODO: handling of raw_id should be a method on repository itself,
55 # which knows how to translate commit index and commit id
55 # which knows how to translate commit index and commit id
56 self.raw_id = commit_id
56 self.raw_id = commit_id
57 self.short_id = commit_id
57 self.short_id = commit_id
58 self.id = 'r%s' % (commit_id, )
58 self.id = 'r%s' % (commit_id, )
59
59
60 # TODO: Implement the following placeholder attributes
60 # TODO: Implement the following placeholder attributes
61 self.nodes = {}
61 self.nodes = {}
62 self.tags = []
62 self.tags = []
63
63
64 @property
64 @property
65 def author(self):
65 def author(self):
66 return safe_unicode(self._properties.get('svn:author'))
66 return safe_unicode(self._properties.get('svn:author'))
67
67
68 @property
68 @property
69 def date(self):
69 def date(self):
70 return _date_from_svn_properties(self._properties)
70 return _date_from_svn_properties(self._properties)
71
71
72 @property
72 @property
73 def message(self):
73 def message(self):
74 return safe_unicode(self._properties.get('svn:log'))
74 return safe_unicode(self._properties.get('svn:log'))
75
75
76 @LazyProperty
76 @LazyProperty
77 def _properties(self):
77 def _properties(self):
78 return self._remote.revision_properties(self._svn_rev)
78 return self._remote.revision_properties(self._svn_rev)
79
79
80 @LazyProperty
80 @LazyProperty
81 def parents(self):
81 def parents(self):
82 parent_idx = self.idx - 1
82 parent_idx = self.idx - 1
83 if parent_idx >= 0:
83 if parent_idx >= 0:
84 parent = self.repository.get_commit(commit_idx=parent_idx)
84 parent = self.repository.get_commit(commit_idx=parent_idx)
85 return [parent]
85 return [parent]
86 return []
86 return []
87
87
88 @LazyProperty
88 @LazyProperty
89 def children(self):
89 def children(self):
90 child_idx = self.idx + 1
90 child_idx = self.idx + 1
91 if child_idx < len(self.repository.commit_ids):
91 if child_idx < len(self.repository.commit_ids):
92 child = self.repository.get_commit(commit_idx=child_idx)
92 child = self.repository.get_commit(commit_idx=child_idx)
93 return [child]
93 return [child]
94 return []
94 return []
95
95
96 def get_file_mode(self, path):
96 def get_file_mode(self, path):
97 # Note: Subversion flags files which are executable with a special
97 # Note: Subversion flags files which are executable with a special
98 # property `svn:executable` which is set to the value ``"*"``.
98 # property `svn:executable` which is set to the value ``"*"``.
99 if self._get_file_property(path, 'svn:executable') == _SVN_PROP_TRUE:
99 if self._get_file_property(path, 'svn:executable') == _SVN_PROP_TRUE:
100 return base.FILEMODE_EXECUTABLE
100 return base.FILEMODE_EXECUTABLE
101 else:
101 else:
102 return base.FILEMODE_DEFAULT
102 return base.FILEMODE_DEFAULT
103
103
104 def is_link(self, path):
104 def is_link(self, path):
105 # Note: Subversion has a flag for special files, the content of the
105 # Note: Subversion has a flag for special files, the content of the
106 # file contains the type of that file.
106 # file contains the type of that file.
107 if self._get_file_property(path, 'svn:special') == _SVN_PROP_TRUE:
107 if self._get_file_property(path, 'svn:special') == _SVN_PROP_TRUE:
108 return self.get_file_content(path).startswith('link')
108 return self.get_file_content(path).startswith('link')
109 return False
109 return False
110
110
111 def is_node_binary(self, path):
111 def is_node_binary(self, path):
112 path = self._fix_path(path)
112 path = self._fix_path(path)
113 return self._remote.is_binary(self._svn_rev, safe_str(path))
113 return self._remote.is_binary(self._svn_rev, safe_str(path))
114
114
115 def _get_file_property(self, path, name):
115 def _get_file_property(self, path, name):
116 file_properties = self._remote.node_properties(
116 file_properties = self._remote.node_properties(
117 safe_str(path), self._svn_rev)
117 safe_str(path), self._svn_rev)
118 return file_properties.get(name)
118 return file_properties.get(name)
119
119
120 def get_file_content(self, path):
120 def get_file_content(self, path):
121 path = self._fix_path(path)
121 path = self._fix_path(path)
122 return self._remote.get_file_content(safe_str(path), self._svn_rev)
122 return self._remote.get_file_content(safe_str(path), self._svn_rev)
123
123
124 def get_file_content_streamed(self, path):
124 def get_file_content_streamed(self, path):
125 path = self._fix_path(path)
125 path = self._fix_path(path)
126 stream_method = getattr(self._remote, 'stream:get_file_content')
126 stream_method = getattr(self._remote, 'stream:get_file_content')
127 return stream_method(safe_str(path), self._svn_rev)
127 return stream_method(safe_str(path), self._svn_rev)
128
128
129 def get_file_size(self, path):
129 def get_file_size(self, path):
130 path = self._fix_path(path)
130 path = self._fix_path(path)
131 return self._remote.get_file_size(safe_str(path), self._svn_rev)
131 return self._remote.get_file_size(safe_str(path), self._svn_rev)
132
132
133 def get_path_history(self, path, limit=None, pre_load=None):
133 def get_path_history(self, path, limit=None, pre_load=None):
134 path = safe_str(self._fix_path(path))
134 path = safe_str(self._fix_path(path))
135 history = self._remote.node_history(path, self._svn_rev, limit)
135 history = self._remote.node_history(path, self._svn_rev, limit)
136 return [
136 return [
137 self.repository.get_commit(commit_id=str(svn_rev))
137 self.repository.get_commit(commit_id=str(svn_rev))
138 for svn_rev in history]
138 for svn_rev in history]
139
139
140 def get_file_annotate(self, path, pre_load=None):
140 def get_file_annotate(self, path, pre_load=None):
141 result = self._remote.file_annotate(safe_str(path), self._svn_rev)
141 result = self._remote.file_annotate(safe_str(path), self._svn_rev)
142
142
143 for zero_based_line_no, svn_rev, content in result:
143 for zero_based_line_no, svn_rev, content in result:
144 commit_id = str(svn_rev)
144 commit_id = str(svn_rev)
145 line_no = zero_based_line_no + 1
145 line_no = zero_based_line_no + 1
146 yield (
146 yield (
147 line_no,
147 line_no,
148 commit_id,
148 commit_id,
149 lambda: self.repository.get_commit(commit_id=commit_id),
149 lambda: self.repository.get_commit(commit_id=commit_id),
150 content)
150 content)
151
151
152 def get_node(self, path, pre_load=None):
152 def get_node(self, path, pre_load=None):
153 path = self._fix_path(path)
153 path = self._fix_path(path)
154 if path not in self.nodes:
154 if path not in self.nodes:
155
155
156 if path == '':
156 if path == '':
157 node = nodes.RootNode(commit=self)
157 node = nodes.RootNode(commit=self)
158 else:
158 else:
159 node_type = self._remote.get_node_type(
159 node_type = self._remote.get_node_type(
160 safe_str(path), self._svn_rev)
160 safe_str(path), self._svn_rev)
161 if node_type == 'dir':
161 if node_type == 'dir':
162 node = nodes.DirNode(path, commit=self)
162 node = nodes.DirNode(path, commit=self)
163 elif node_type == 'file':
163 elif node_type == 'file':
164 node = nodes.FileNode(path, commit=self, pre_load=pre_load)
164 node = nodes.FileNode(path, commit=self, pre_load=pre_load)
165 else:
165 else:
166 raise self.no_node_at_path(path)
166 raise self.no_node_at_path(path)
167
167
168 self.nodes[path] = node
168 self.nodes[path] = node
169 return self.nodes[path]
169 return self.nodes[path]
170
170
171 def get_nodes(self, path):
171 def get_nodes(self, path):
172 if self._get_kind(path) != nodes.NodeKind.DIR:
172 if self._get_kind(path) != nodes.NodeKind.DIR:
173 raise CommitError(
173 raise CommitError(
174 "Directory does not exist for commit %s at "
174 "Directory does not exist for commit %s at "
175 " '%s'" % (self.raw_id, path))
175 " '%s'" % (self.raw_id, path))
176 path = self._fix_path(path)
176 path = safe_str(self._fix_path(path))
177
177
178 path_nodes = []
178 path_nodes = []
179 for name, kind in self._remote.get_nodes(
179 for name, kind in self._remote.get_nodes(path, revision=self._svn_rev):
180 safe_str(path), revision=self._svn_rev):
181 node_path = vcspath.join(path, name)
180 node_path = vcspath.join(path, name)
182 if kind == 'dir':
181 if kind == 'dir':
183 node = nodes.DirNode(node_path, commit=self)
182 node = nodes.DirNode(node_path, commit=self)
184 elif kind == 'file':
183 elif kind == 'file':
185 node = nodes.FileNode(node_path, commit=self)
184 node = nodes.FileNode(node_path, commit=self)
186 else:
185 else:
187 raise ValueError("Node kind %s not supported." % (kind, ))
186 raise ValueError("Node kind %s not supported." % (kind, ))
188 self.nodes[node_path] = node
187 self.nodes[node_path] = node
189 path_nodes.append(node)
188 path_nodes.append(node)
190
189
191 return path_nodes
190 return path_nodes
192
191
193 def _get_kind(self, path):
192 def _get_kind(self, path):
194 path = self._fix_path(path)
193 path = self._fix_path(path)
195 kind = self._remote.get_node_type(path, self._svn_rev)
194 kind = self._remote.get_node_type(path, self._svn_rev)
196 if kind == 'file':
195 if kind == 'file':
197 return nodes.NodeKind.FILE
196 return nodes.NodeKind.FILE
198 elif kind == 'dir':
197 elif kind == 'dir':
199 return nodes.NodeKind.DIR
198 return nodes.NodeKind.DIR
200 else:
199 else:
201 raise CommitError(
200 raise CommitError(
202 "Node does not exist at the given path '%s'" % (path, ))
201 "Node does not exist at the given path '%s'" % (path, ))
203
202
204 @LazyProperty
203 @LazyProperty
205 def _changes_cache(self):
204 def _changes_cache(self):
206 return self._remote.revision_changes(self._svn_rev)
205 return self._remote.revision_changes(self._svn_rev)
207
206
208 @LazyProperty
207 @LazyProperty
209 def affected_files(self):
208 def affected_files(self):
210 changed_files = set()
209 changed_files = set()
211 for files in self._changes_cache.itervalues():
210 for files in self._changes_cache.itervalues():
212 changed_files.update(files)
211 changed_files.update(files)
213 return list(changed_files)
212 return list(changed_files)
214
213
215 @LazyProperty
214 @LazyProperty
216 def id(self):
215 def id(self):
217 return self.raw_id
216 return self.raw_id
218
217
219 @property
218 @property
220 def added(self):
219 def added(self):
221 return nodes.AddedFileNodesGenerator(self.added_paths, self)
220 return nodes.AddedFileNodesGenerator(self.added_paths, self)
222
221
223 @LazyProperty
222 @LazyProperty
224 def added_paths(self):
223 def added_paths(self):
225 return [n for n in self._changes_cache['added']]
224 return [n for n in self._changes_cache['added']]
226
225
227 @property
226 @property
228 def changed(self):
227 def changed(self):
229 return nodes.ChangedFileNodesGenerator(self.changed_paths, self)
228 return nodes.ChangedFileNodesGenerator(self.changed_paths, self)
230
229
231 @LazyProperty
230 @LazyProperty
232 def changed_paths(self):
231 def changed_paths(self):
233 return [n for n in self._changes_cache['changed']]
232 return [n for n in self._changes_cache['changed']]
234
233
235 @property
234 @property
236 def removed(self):
235 def removed(self):
237 return nodes.RemovedFileNodesGenerator(self.removed_paths, self)
236 return nodes.RemovedFileNodesGenerator(self.removed_paths, self)
238
237
239 @LazyProperty
238 @LazyProperty
240 def removed_paths(self):
239 def removed_paths(self):
241 return [n for n in self._changes_cache['removed']]
240 return [n for n in self._changes_cache['removed']]
242
241
243
242
244 def _date_from_svn_properties(properties):
243 def _date_from_svn_properties(properties):
245 """
244 """
246 Parses the date out of given svn properties.
245 Parses the date out of given svn properties.
247
246
248 :return: :class:`datetime.datetime` instance. The object is naive.
247 :return: :class:`datetime.datetime` instance. The object is naive.
249 """
248 """
250
249
251 aware_date = dateutil.parser.parse(properties.get('svn:date'))
250 aware_date = dateutil.parser.parse(properties.get('svn:date'))
252 # final_date = aware_date.astimezone(dateutil.tz.tzlocal())
251 # final_date = aware_date.astimezone(dateutil.tz.tzlocal())
253 final_date = aware_date
252 final_date = aware_date
254 return final_date.replace(tzinfo=None)
253 return final_date.replace(tzinfo=None)
General Comments 0
You need to be logged in to leave comments. Login now