##// END OF EJS Templates
svn: implement run svn_command for svn repositories.
marcink -
r671:5115806e default
parent child Browse files
Show More
@@ -1,732 +1,765 b''
1 # RhodeCode VCSServer provides access to different vcs backends via network.
1 # RhodeCode VCSServer provides access to different vcs backends via network.
2 # Copyright (C) 2014-2019 RhodeCode GmbH
2 # Copyright (C) 2014-2019 RhodeCode GmbH
3 #
3 #
4 # This program is free software; you can redistribute it and/or modify
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 3 of the License, or
6 # the Free Software Foundation; either version 3 of the License, or
7 # (at your option) any later version.
7 # (at your option) any later version.
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 General Public License
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software Foundation,
15 # along with this program; if not, write to the Free Software Foundation,
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
16 # Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
17
17
18 from __future__ import absolute_import
18 from __future__ import absolute_import
19
19
20 import os
20 import os
21 import subprocess
21 import subprocess
22 from urllib2 import URLError
22 from urllib2 import URLError
23 import urlparse
23 import urlparse
24 import logging
24 import logging
25 import posixpath as vcspath
25 import posixpath as vcspath
26 import StringIO
26 import StringIO
27 import urllib
27 import urllib
28 import traceback
28 import traceback
29
29
30 import svn.client
30 import svn.client
31 import svn.core
31 import svn.core
32 import svn.delta
32 import svn.delta
33 import svn.diff
33 import svn.diff
34 import svn.fs
34 import svn.fs
35 import svn.repos
35 import svn.repos
36
36
37 from vcsserver import svn_diff, exceptions, subprocessio, settings
37 from vcsserver import svn_diff, exceptions, subprocessio, settings
38 from vcsserver.base import RepoFactory, raise_from_original
38 from vcsserver.base import RepoFactory, raise_from_original
39
39
40 log = logging.getLogger(__name__)
40 log = logging.getLogger(__name__)
41
41
42
42
43 # Set of svn compatible version flags.
43 # Set of svn compatible version flags.
44 # Compare with subversion/svnadmin/svnadmin.c
44 # Compare with subversion/svnadmin/svnadmin.c
45 svn_compatible_versions = {
45 svn_compatible_versions = {
46 'pre-1.4-compatible',
46 'pre-1.4-compatible',
47 'pre-1.5-compatible',
47 'pre-1.5-compatible',
48 'pre-1.6-compatible',
48 'pre-1.6-compatible',
49 'pre-1.8-compatible',
49 'pre-1.8-compatible',
50 'pre-1.9-compatible'
50 'pre-1.9-compatible'
51 }
51 }
52
52
53 svn_compatible_versions_map = {
53 svn_compatible_versions_map = {
54 'pre-1.4-compatible': '1.3',
54 'pre-1.4-compatible': '1.3',
55 'pre-1.5-compatible': '1.4',
55 'pre-1.5-compatible': '1.4',
56 'pre-1.6-compatible': '1.5',
56 'pre-1.6-compatible': '1.5',
57 'pre-1.8-compatible': '1.7',
57 'pre-1.8-compatible': '1.7',
58 'pre-1.9-compatible': '1.8',
58 'pre-1.9-compatible': '1.8',
59 }
59 }
60
60
61
61
62 def reraise_safe_exceptions(func):
62 def reraise_safe_exceptions(func):
63 """Decorator for converting svn exceptions to something neutral."""
63 """Decorator for converting svn exceptions to something neutral."""
64 def wrapper(*args, **kwargs):
64 def wrapper(*args, **kwargs):
65 try:
65 try:
66 return func(*args, **kwargs)
66 return func(*args, **kwargs)
67 except Exception as e:
67 except Exception as e:
68 if not hasattr(e, '_vcs_kind'):
68 if not hasattr(e, '_vcs_kind'):
69 log.exception("Unhandled exception in svn remote call")
69 log.exception("Unhandled exception in svn remote call")
70 raise_from_original(exceptions.UnhandledException(e))
70 raise_from_original(exceptions.UnhandledException(e))
71 raise
71 raise
72 return wrapper
72 return wrapper
73
73
74
74
75 class SubversionFactory(RepoFactory):
75 class SubversionFactory(RepoFactory):
76 repo_type = 'svn'
76 repo_type = 'svn'
77
77
78 def _create_repo(self, wire, create, compatible_version):
78 def _create_repo(self, wire, create, compatible_version):
79 path = svn.core.svn_path_canonicalize(wire['path'])
79 path = svn.core.svn_path_canonicalize(wire['path'])
80 if create:
80 if create:
81 fs_config = {'compatible-version': '1.9'}
81 fs_config = {'compatible-version': '1.9'}
82 if compatible_version:
82 if compatible_version:
83 if compatible_version not in svn_compatible_versions:
83 if compatible_version not in svn_compatible_versions:
84 raise Exception('Unknown SVN compatible version "{}"'
84 raise Exception('Unknown SVN compatible version "{}"'
85 .format(compatible_version))
85 .format(compatible_version))
86 fs_config['compatible-version'] = \
86 fs_config['compatible-version'] = \
87 svn_compatible_versions_map[compatible_version]
87 svn_compatible_versions_map[compatible_version]
88
88
89 log.debug('Create SVN repo with config "%s"', fs_config)
89 log.debug('Create SVN repo with config "%s"', fs_config)
90 repo = svn.repos.create(path, "", "", None, fs_config)
90 repo = svn.repos.create(path, "", "", None, fs_config)
91 else:
91 else:
92 repo = svn.repos.open(path)
92 repo = svn.repos.open(path)
93
93
94 log.debug('Got SVN object: %s', repo)
94 log.debug('Got SVN object: %s', repo)
95 return repo
95 return repo
96
96
97 def repo(self, wire, create=False, compatible_version=None):
97 def repo(self, wire, create=False, compatible_version=None):
98 """
98 """
99 Get a repository instance for the given path.
99 Get a repository instance for the given path.
100
100
101 Uses internally the low level beaker API since the decorators introduce
101 Uses internally the low level beaker API since the decorators introduce
102 significant overhead.
102 significant overhead.
103 """
103 """
104 region = self._cache_region
104 region = self._cache_region
105 context = wire.get('context', None)
105 context = wire.get('context', None)
106 repo_path = wire.get('path', '')
106 repo_path = wire.get('path', '')
107 context_uid = '{}'.format(context)
107 context_uid = '{}'.format(context)
108 cache = wire.get('cache', True)
108 cache = wire.get('cache', True)
109 cache_on = context and cache
109 cache_on = context and cache
110
110
111 @region.conditional_cache_on_arguments(condition=cache_on)
111 @region.conditional_cache_on_arguments(condition=cache_on)
112 def create_new_repo(_repo_type, _repo_path, _context_uid, compatible_version_id):
112 def create_new_repo(_repo_type, _repo_path, _context_uid, compatible_version_id):
113 return self._create_repo(wire, create, compatible_version)
113 return self._create_repo(wire, create, compatible_version)
114
114
115 return create_new_repo(self.repo_type, repo_path, context_uid,
115 return create_new_repo(self.repo_type, repo_path, context_uid,
116 compatible_version)
116 compatible_version)
117
117
118
118
119 NODE_TYPE_MAPPING = {
119 NODE_TYPE_MAPPING = {
120 svn.core.svn_node_file: 'file',
120 svn.core.svn_node_file: 'file',
121 svn.core.svn_node_dir: 'dir',
121 svn.core.svn_node_dir: 'dir',
122 }
122 }
123
123
124
124
125 class SvnRemote(object):
125 class SvnRemote(object):
126
126
127 def __init__(self, factory, hg_factory=None):
127 def __init__(self, factory, hg_factory=None):
128 self._factory = factory
128 self._factory = factory
129 # TODO: Remove once we do not use internal Mercurial objects anymore
129 # TODO: Remove once we do not use internal Mercurial objects anymore
130 # for subversion
130 # for subversion
131 self._hg_factory = hg_factory
131 self._hg_factory = hg_factory
132
132
133 @reraise_safe_exceptions
133 @reraise_safe_exceptions
134 def discover_svn_version(self):
134 def discover_svn_version(self):
135 try:
135 try:
136 import svn.core
136 import svn.core
137 svn_ver = svn.core.SVN_VERSION
137 svn_ver = svn.core.SVN_VERSION
138 except ImportError:
138 except ImportError:
139 svn_ver = None
139 svn_ver = None
140 return svn_ver
140 return svn_ver
141
141
142 def check_url(self, url, config_items):
142 def check_url(self, url, config_items):
143 # this can throw exception if not installed, but we detect this
143 # this can throw exception if not installed, but we detect this
144 from hgsubversion import svnrepo
144 from hgsubversion import svnrepo
145
145
146 baseui = self._hg_factory._create_config(config_items)
146 baseui = self._hg_factory._create_config(config_items)
147 # uuid function get's only valid UUID from proper repo, else
147 # uuid function get's only valid UUID from proper repo, else
148 # throws exception
148 # throws exception
149 try:
149 try:
150 svnrepo.svnremoterepo(baseui, url).svn.uuid
150 svnrepo.svnremoterepo(baseui, url).svn.uuid
151 except Exception:
151 except Exception:
152 tb = traceback.format_exc()
152 tb = traceback.format_exc()
153 log.debug("Invalid Subversion url: `%s`, tb: %s", url, tb)
153 log.debug("Invalid Subversion url: `%s`, tb: %s", url, tb)
154 raise URLError(
154 raise URLError(
155 '"%s" is not a valid Subversion source url.' % (url, ))
155 '"%s" is not a valid Subversion source url.' % (url, ))
156 return True
156 return True
157
157
158 def is_path_valid_repository(self, wire, path):
158 def is_path_valid_repository(self, wire, path):
159
159
160 # NOTE(marcink): short circuit the check for SVN repo
160 # NOTE(marcink): short circuit the check for SVN repo
161 # the repos.open might be expensive to check, but we have one cheap
161 # the repos.open might be expensive to check, but we have one cheap
162 # pre condition that we can use, to check for 'format' file
162 # pre condition that we can use, to check for 'format' file
163
163
164 if not os.path.isfile(os.path.join(path, 'format')):
164 if not os.path.isfile(os.path.join(path, 'format')):
165 return False
165 return False
166
166
167 try:
167 try:
168 svn.repos.open(path)
168 svn.repos.open(path)
169 except svn.core.SubversionException:
169 except svn.core.SubversionException:
170 tb = traceback.format_exc()
170 tb = traceback.format_exc()
171 log.debug("Invalid Subversion path `%s`, tb: %s", path, tb)
171 log.debug("Invalid Subversion path `%s`, tb: %s", path, tb)
172 return False
172 return False
173 return True
173 return True
174
174
175 @reraise_safe_exceptions
175 @reraise_safe_exceptions
176 def verify(self, wire,):
176 def verify(self, wire,):
177 repo_path = wire['path']
177 repo_path = wire['path']
178 if not self.is_path_valid_repository(wire, repo_path):
178 if not self.is_path_valid_repository(wire, repo_path):
179 raise Exception(
179 raise Exception(
180 "Path %s is not a valid Subversion repository." % repo_path)
180 "Path %s is not a valid Subversion repository." % repo_path)
181
181
182 cmd = ['svnadmin', 'info', repo_path]
182 cmd = ['svnadmin', 'info', repo_path]
183 stdout, stderr = subprocessio.run_command(cmd)
183 stdout, stderr = subprocessio.run_command(cmd)
184 return stdout
184 return stdout
185
185
186 def lookup(self, wire, revision):
186 def lookup(self, wire, revision):
187 if revision not in [-1, None, 'HEAD']:
187 if revision not in [-1, None, 'HEAD']:
188 raise NotImplementedError
188 raise NotImplementedError
189 repo = self._factory.repo(wire)
189 repo = self._factory.repo(wire)
190 fs_ptr = svn.repos.fs(repo)
190 fs_ptr = svn.repos.fs(repo)
191 head = svn.fs.youngest_rev(fs_ptr)
191 head = svn.fs.youngest_rev(fs_ptr)
192 return head
192 return head
193
193
194 def lookup_interval(self, wire, start_ts, end_ts):
194 def lookup_interval(self, wire, start_ts, end_ts):
195 repo = self._factory.repo(wire)
195 repo = self._factory.repo(wire)
196 fsobj = svn.repos.fs(repo)
196 fsobj = svn.repos.fs(repo)
197 start_rev = None
197 start_rev = None
198 end_rev = None
198 end_rev = None
199 if start_ts:
199 if start_ts:
200 start_ts_svn = apr_time_t(start_ts)
200 start_ts_svn = apr_time_t(start_ts)
201 start_rev = svn.repos.dated_revision(repo, start_ts_svn) + 1
201 start_rev = svn.repos.dated_revision(repo, start_ts_svn) + 1
202 else:
202 else:
203 start_rev = 1
203 start_rev = 1
204 if end_ts:
204 if end_ts:
205 end_ts_svn = apr_time_t(end_ts)
205 end_ts_svn = apr_time_t(end_ts)
206 end_rev = svn.repos.dated_revision(repo, end_ts_svn)
206 end_rev = svn.repos.dated_revision(repo, end_ts_svn)
207 else:
207 else:
208 end_rev = svn.fs.youngest_rev(fsobj)
208 end_rev = svn.fs.youngest_rev(fsobj)
209 return start_rev, end_rev
209 return start_rev, end_rev
210
210
211 def revision_properties(self, wire, revision):
211 def revision_properties(self, wire, revision):
212 repo = self._factory.repo(wire)
212 repo = self._factory.repo(wire)
213 fs_ptr = svn.repos.fs(repo)
213 fs_ptr = svn.repos.fs(repo)
214 return svn.fs.revision_proplist(fs_ptr, revision)
214 return svn.fs.revision_proplist(fs_ptr, revision)
215
215
216 def revision_changes(self, wire, revision):
216 def revision_changes(self, wire, revision):
217
217
218 repo = self._factory.repo(wire)
218 repo = self._factory.repo(wire)
219 fsobj = svn.repos.fs(repo)
219 fsobj = svn.repos.fs(repo)
220 rev_root = svn.fs.revision_root(fsobj, revision)
220 rev_root = svn.fs.revision_root(fsobj, revision)
221
221
222 editor = svn.repos.ChangeCollector(fsobj, rev_root)
222 editor = svn.repos.ChangeCollector(fsobj, rev_root)
223 editor_ptr, editor_baton = svn.delta.make_editor(editor)
223 editor_ptr, editor_baton = svn.delta.make_editor(editor)
224 base_dir = ""
224 base_dir = ""
225 send_deltas = False
225 send_deltas = False
226 svn.repos.replay2(
226 svn.repos.replay2(
227 rev_root, base_dir, svn.core.SVN_INVALID_REVNUM, send_deltas,
227 rev_root, base_dir, svn.core.SVN_INVALID_REVNUM, send_deltas,
228 editor_ptr, editor_baton, None)
228 editor_ptr, editor_baton, None)
229
229
230 added = []
230 added = []
231 changed = []
231 changed = []
232 removed = []
232 removed = []
233
233
234 # TODO: CHANGE_ACTION_REPLACE: Figure out where it belongs
234 # TODO: CHANGE_ACTION_REPLACE: Figure out where it belongs
235 for path, change in editor.changes.iteritems():
235 for path, change in editor.changes.iteritems():
236 # TODO: Decide what to do with directory nodes. Subversion can add
236 # TODO: Decide what to do with directory nodes. Subversion can add
237 # empty directories.
237 # empty directories.
238
238
239 if change.item_kind == svn.core.svn_node_dir:
239 if change.item_kind == svn.core.svn_node_dir:
240 continue
240 continue
241 if change.action in [svn.repos.CHANGE_ACTION_ADD]:
241 if change.action in [svn.repos.CHANGE_ACTION_ADD]:
242 added.append(path)
242 added.append(path)
243 elif change.action in [svn.repos.CHANGE_ACTION_MODIFY,
243 elif change.action in [svn.repos.CHANGE_ACTION_MODIFY,
244 svn.repos.CHANGE_ACTION_REPLACE]:
244 svn.repos.CHANGE_ACTION_REPLACE]:
245 changed.append(path)
245 changed.append(path)
246 elif change.action in [svn.repos.CHANGE_ACTION_DELETE]:
246 elif change.action in [svn.repos.CHANGE_ACTION_DELETE]:
247 removed.append(path)
247 removed.append(path)
248 else:
248 else:
249 raise NotImplementedError(
249 raise NotImplementedError(
250 "Action %s not supported on path %s" % (
250 "Action %s not supported on path %s" % (
251 change.action, path))
251 change.action, path))
252
252
253 changes = {
253 changes = {
254 'added': added,
254 'added': added,
255 'changed': changed,
255 'changed': changed,
256 'removed': removed,
256 'removed': removed,
257 }
257 }
258 return changes
258 return changes
259
259
260 def node_history(self, wire, path, revision, limit):
260 def node_history(self, wire, path, revision, limit):
261 cross_copies = False
261 cross_copies = False
262 repo = self._factory.repo(wire)
262 repo = self._factory.repo(wire)
263 fsobj = svn.repos.fs(repo)
263 fsobj = svn.repos.fs(repo)
264 rev_root = svn.fs.revision_root(fsobj, revision)
264 rev_root = svn.fs.revision_root(fsobj, revision)
265
265
266 history_revisions = []
266 history_revisions = []
267 history = svn.fs.node_history(rev_root, path)
267 history = svn.fs.node_history(rev_root, path)
268 history = svn.fs.history_prev(history, cross_copies)
268 history = svn.fs.history_prev(history, cross_copies)
269 while history:
269 while history:
270 __, node_revision = svn.fs.history_location(history)
270 __, node_revision = svn.fs.history_location(history)
271 history_revisions.append(node_revision)
271 history_revisions.append(node_revision)
272 if limit and len(history_revisions) >= limit:
272 if limit and len(history_revisions) >= limit:
273 break
273 break
274 history = svn.fs.history_prev(history, cross_copies)
274 history = svn.fs.history_prev(history, cross_copies)
275 return history_revisions
275 return history_revisions
276
276
277 def node_properties(self, wire, path, revision):
277 def node_properties(self, wire, path, revision):
278 repo = self._factory.repo(wire)
278 repo = self._factory.repo(wire)
279 fsobj = svn.repos.fs(repo)
279 fsobj = svn.repos.fs(repo)
280 rev_root = svn.fs.revision_root(fsobj, revision)
280 rev_root = svn.fs.revision_root(fsobj, revision)
281 return svn.fs.node_proplist(rev_root, path)
281 return svn.fs.node_proplist(rev_root, path)
282
282
283 def file_annotate(self, wire, path, revision):
283 def file_annotate(self, wire, path, revision):
284 abs_path = 'file://' + urllib.pathname2url(
284 abs_path = 'file://' + urllib.pathname2url(
285 vcspath.join(wire['path'], path))
285 vcspath.join(wire['path'], path))
286 file_uri = svn.core.svn_path_canonicalize(abs_path)
286 file_uri = svn.core.svn_path_canonicalize(abs_path)
287
287
288 start_rev = svn_opt_revision_value_t(0)
288 start_rev = svn_opt_revision_value_t(0)
289 peg_rev = svn_opt_revision_value_t(revision)
289 peg_rev = svn_opt_revision_value_t(revision)
290 end_rev = peg_rev
290 end_rev = peg_rev
291
291
292 annotations = []
292 annotations = []
293
293
294 def receiver(line_no, revision, author, date, line, pool):
294 def receiver(line_no, revision, author, date, line, pool):
295 annotations.append((line_no, revision, line))
295 annotations.append((line_no, revision, line))
296
296
297 # TODO: Cannot use blame5, missing typemap function in the swig code
297 # TODO: Cannot use blame5, missing typemap function in the swig code
298 try:
298 try:
299 svn.client.blame2(
299 svn.client.blame2(
300 file_uri, peg_rev, start_rev, end_rev,
300 file_uri, peg_rev, start_rev, end_rev,
301 receiver, svn.client.create_context())
301 receiver, svn.client.create_context())
302 except svn.core.SubversionException as exc:
302 except svn.core.SubversionException as exc:
303 log.exception("Error during blame operation.")
303 log.exception("Error during blame operation.")
304 raise Exception(
304 raise Exception(
305 "Blame not supported or file does not exist at path %s. "
305 "Blame not supported or file does not exist at path %s. "
306 "Error %s." % (path, exc))
306 "Error %s." % (path, exc))
307
307
308 return annotations
308 return annotations
309
309
310 def get_node_type(self, wire, path, rev=None):
310 def get_node_type(self, wire, path, rev=None):
311 repo = self._factory.repo(wire)
311 repo = self._factory.repo(wire)
312 fs_ptr = svn.repos.fs(repo)
312 fs_ptr = svn.repos.fs(repo)
313 if rev is None:
313 if rev is None:
314 rev = svn.fs.youngest_rev(fs_ptr)
314 rev = svn.fs.youngest_rev(fs_ptr)
315 root = svn.fs.revision_root(fs_ptr, rev)
315 root = svn.fs.revision_root(fs_ptr, rev)
316 node = svn.fs.check_path(root, path)
316 node = svn.fs.check_path(root, path)
317 return NODE_TYPE_MAPPING.get(node, None)
317 return NODE_TYPE_MAPPING.get(node, None)
318
318
319 def get_nodes(self, wire, path, revision=None):
319 def get_nodes(self, wire, path, revision=None):
320 repo = self._factory.repo(wire)
320 repo = self._factory.repo(wire)
321 fsobj = svn.repos.fs(repo)
321 fsobj = svn.repos.fs(repo)
322 if revision is None:
322 if revision is None:
323 revision = svn.fs.youngest_rev(fsobj)
323 revision = svn.fs.youngest_rev(fsobj)
324 root = svn.fs.revision_root(fsobj, revision)
324 root = svn.fs.revision_root(fsobj, revision)
325 entries = svn.fs.dir_entries(root, path)
325 entries = svn.fs.dir_entries(root, path)
326 result = []
326 result = []
327 for entry_path, entry_info in entries.iteritems():
327 for entry_path, entry_info in entries.iteritems():
328 result.append(
328 result.append(
329 (entry_path, NODE_TYPE_MAPPING.get(entry_info.kind, None)))
329 (entry_path, NODE_TYPE_MAPPING.get(entry_info.kind, None)))
330 return result
330 return result
331
331
332 def get_file_content(self, wire, path, rev=None):
332 def get_file_content(self, wire, path, rev=None):
333 repo = self._factory.repo(wire)
333 repo = self._factory.repo(wire)
334 fsobj = svn.repos.fs(repo)
334 fsobj = svn.repos.fs(repo)
335 if rev is None:
335 if rev is None:
336 rev = svn.fs.youngest_revision(fsobj)
336 rev = svn.fs.youngest_revision(fsobj)
337 root = svn.fs.revision_root(fsobj, rev)
337 root = svn.fs.revision_root(fsobj, rev)
338 content = svn.core.Stream(svn.fs.file_contents(root, path))
338 content = svn.core.Stream(svn.fs.file_contents(root, path))
339 return content.read()
339 return content.read()
340
340
341 def get_file_size(self, wire, path, revision=None):
341 def get_file_size(self, wire, path, revision=None):
342 repo = self._factory.repo(wire)
342 repo = self._factory.repo(wire)
343 fsobj = svn.repos.fs(repo)
343 fsobj = svn.repos.fs(repo)
344 if revision is None:
344 if revision is None:
345 revision = svn.fs.youngest_revision(fsobj)
345 revision = svn.fs.youngest_revision(fsobj)
346 root = svn.fs.revision_root(fsobj, revision)
346 root = svn.fs.revision_root(fsobj, revision)
347 size = svn.fs.file_length(root, path)
347 size = svn.fs.file_length(root, path)
348 return size
348 return size
349
349
350 def create_repository(self, wire, compatible_version=None):
350 def create_repository(self, wire, compatible_version=None):
351 log.info('Creating Subversion repository in path "%s"', wire['path'])
351 log.info('Creating Subversion repository in path "%s"', wire['path'])
352 self._factory.repo(wire, create=True,
352 self._factory.repo(wire, create=True,
353 compatible_version=compatible_version)
353 compatible_version=compatible_version)
354
354
355 def get_url_and_credentials(self, src_url):
355 def get_url_and_credentials(self, src_url):
356 obj = urlparse.urlparse(src_url)
356 obj = urlparse.urlparse(src_url)
357 username = obj.username or None
357 username = obj.username or None
358 password = obj.password or None
358 password = obj.password or None
359 return username, password, src_url
359 return username, password, src_url
360
360
361 def import_remote_repository(self, wire, src_url):
361 def import_remote_repository(self, wire, src_url):
362 repo_path = wire['path']
362 repo_path = wire['path']
363 if not self.is_path_valid_repository(wire, repo_path):
363 if not self.is_path_valid_repository(wire, repo_path):
364 raise Exception(
364 raise Exception(
365 "Path %s is not a valid Subversion repository." % repo_path)
365 "Path %s is not a valid Subversion repository." % repo_path)
366
366
367 username, password, src_url = self.get_url_and_credentials(src_url)
367 username, password, src_url = self.get_url_and_credentials(src_url)
368 rdump_cmd = ['svnrdump', 'dump', '--non-interactive',
368 rdump_cmd = ['svnrdump', 'dump', '--non-interactive',
369 '--trust-server-cert-failures=unknown-ca']
369 '--trust-server-cert-failures=unknown-ca']
370 if username and password:
370 if username and password:
371 rdump_cmd += ['--username', username, '--password', password]
371 rdump_cmd += ['--username', username, '--password', password]
372 rdump_cmd += [src_url]
372 rdump_cmd += [src_url]
373
373
374 rdump = subprocess.Popen(
374 rdump = subprocess.Popen(
375 rdump_cmd,
375 rdump_cmd,
376 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
376 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
377 load = subprocess.Popen(
377 load = subprocess.Popen(
378 ['svnadmin', 'load', repo_path], stdin=rdump.stdout)
378 ['svnadmin', 'load', repo_path], stdin=rdump.stdout)
379
379
380 # TODO: johbo: This can be a very long operation, might be better
380 # TODO: johbo: This can be a very long operation, might be better
381 # to track some kind of status and provide an api to check if the
381 # to track some kind of status and provide an api to check if the
382 # import is done.
382 # import is done.
383 rdump.wait()
383 rdump.wait()
384 load.wait()
384 load.wait()
385
385
386 log.debug('Return process ended with code: %s', rdump.returncode)
386 log.debug('Return process ended with code: %s', rdump.returncode)
387 if rdump.returncode != 0:
387 if rdump.returncode != 0:
388 errors = rdump.stderr.read()
388 errors = rdump.stderr.read()
389 log.error('svnrdump dump failed: statuscode %s: message: %s',
389 log.error('svnrdump dump failed: statuscode %s: message: %s',
390 rdump.returncode, errors)
390 rdump.returncode, errors)
391 reason = 'UNKNOWN'
391 reason = 'UNKNOWN'
392 if 'svnrdump: E230001:' in errors:
392 if 'svnrdump: E230001:' in errors:
393 reason = 'INVALID_CERTIFICATE'
393 reason = 'INVALID_CERTIFICATE'
394
394
395 if reason == 'UNKNOWN':
395 if reason == 'UNKNOWN':
396 reason = 'UNKNOWN:{}'.format(errors)
396 reason = 'UNKNOWN:{}'.format(errors)
397 raise Exception(
397 raise Exception(
398 'Failed to dump the remote repository from %s. Reason:%s' % (
398 'Failed to dump the remote repository from %s. Reason:%s' % (
399 src_url, reason))
399 src_url, reason))
400 if load.returncode != 0:
400 if load.returncode != 0:
401 raise Exception(
401 raise Exception(
402 'Failed to load the dump of remote repository from %s.' %
402 'Failed to load the dump of remote repository from %s.' %
403 (src_url, ))
403 (src_url, ))
404
404
405 def commit(self, wire, message, author, timestamp, updated, removed):
405 def commit(self, wire, message, author, timestamp, updated, removed):
406 assert isinstance(message, str)
406 assert isinstance(message, str)
407 assert isinstance(author, str)
407 assert isinstance(author, str)
408
408
409 repo = self._factory.repo(wire)
409 repo = self._factory.repo(wire)
410 fsobj = svn.repos.fs(repo)
410 fsobj = svn.repos.fs(repo)
411
411
412 rev = svn.fs.youngest_rev(fsobj)
412 rev = svn.fs.youngest_rev(fsobj)
413 txn = svn.repos.fs_begin_txn_for_commit(repo, rev, author, message)
413 txn = svn.repos.fs_begin_txn_for_commit(repo, rev, author, message)
414 txn_root = svn.fs.txn_root(txn)
414 txn_root = svn.fs.txn_root(txn)
415
415
416 for node in updated:
416 for node in updated:
417 TxnNodeProcessor(node, txn_root).update()
417 TxnNodeProcessor(node, txn_root).update()
418 for node in removed:
418 for node in removed:
419 TxnNodeProcessor(node, txn_root).remove()
419 TxnNodeProcessor(node, txn_root).remove()
420
420
421 commit_id = svn.repos.fs_commit_txn(repo, txn)
421 commit_id = svn.repos.fs_commit_txn(repo, txn)
422
422
423 if timestamp:
423 if timestamp:
424 apr_time = apr_time_t(timestamp)
424 apr_time = apr_time_t(timestamp)
425 ts_formatted = svn.core.svn_time_to_cstring(apr_time)
425 ts_formatted = svn.core.svn_time_to_cstring(apr_time)
426 svn.fs.change_rev_prop(fsobj, commit_id, 'svn:date', ts_formatted)
426 svn.fs.change_rev_prop(fsobj, commit_id, 'svn:date', ts_formatted)
427
427
428 log.debug('Committed revision "%s" to "%s".', commit_id, wire['path'])
428 log.debug('Committed revision "%s" to "%s".', commit_id, wire['path'])
429 return commit_id
429 return commit_id
430
430
431 def diff(self, wire, rev1, rev2, path1=None, path2=None,
431 def diff(self, wire, rev1, rev2, path1=None, path2=None,
432 ignore_whitespace=False, context=3):
432 ignore_whitespace=False, context=3):
433
433
434 wire.update(cache=False)
434 wire.update(cache=False)
435 repo = self._factory.repo(wire)
435 repo = self._factory.repo(wire)
436 diff_creator = SvnDiffer(
436 diff_creator = SvnDiffer(
437 repo, rev1, path1, rev2, path2, ignore_whitespace, context)
437 repo, rev1, path1, rev2, path2, ignore_whitespace, context)
438 try:
438 try:
439 return diff_creator.generate_diff()
439 return diff_creator.generate_diff()
440 except svn.core.SubversionException as e:
440 except svn.core.SubversionException as e:
441 log.exception(
441 log.exception(
442 "Error during diff operation operation. "
442 "Error during diff operation operation. "
443 "Path might not exist %s, %s" % (path1, path2))
443 "Path might not exist %s, %s" % (path1, path2))
444 return ""
444 return ""
445
445
446 @reraise_safe_exceptions
446 @reraise_safe_exceptions
447 def is_large_file(self, wire, path):
447 def is_large_file(self, wire, path):
448 return False
448 return False
449
449
450 @reraise_safe_exceptions
450 @reraise_safe_exceptions
451 def run_svn_command(self, wire, cmd, **opts):
452 path = wire.get('path', None)
453
454 if path and os.path.isdir(path):
455 opts['cwd'] = path
456
457 safe_call = False
458 if '_safe' in opts:
459 safe_call = True
460
461 svnenv = os.environ.copy()
462 svnenv.update(opts.pop('extra_env', {}))
463
464 _opts = {'env': svnenv, 'shell': False}
465
466 try:
467 _opts.update(opts)
468 p = subprocessio.SubprocessIOChunker(cmd, **_opts)
469
470 return ''.join(p), ''.join(p.error)
471 except (EnvironmentError, OSError) as err:
472 cmd = ' '.join(cmd) # human friendly CMD
473 tb_err = ("Couldn't run svn command (%s).\n"
474 "Original error was:%s\n"
475 "Call options:%s\n"
476 % (cmd, err, _opts))
477 log.exception(tb_err)
478 if safe_call:
479 return '', err
480 else:
481 raise exceptions.VcsException()(tb_err)
482
483 @reraise_safe_exceptions
451 def install_hooks(self, wire, force=False):
484 def install_hooks(self, wire, force=False):
452 from vcsserver.hook_utils import install_svn_hooks
485 from vcsserver.hook_utils import install_svn_hooks
453 repo_path = wire['path']
486 repo_path = wire['path']
454 binary_dir = settings.BINARY_DIR
487 binary_dir = settings.BINARY_DIR
455 executable = None
488 executable = None
456 if binary_dir:
489 if binary_dir:
457 executable = os.path.join(binary_dir, 'python')
490 executable = os.path.join(binary_dir, 'python')
458 return install_svn_hooks(
491 return install_svn_hooks(
459 repo_path, executable=executable, force_create=force)
492 repo_path, executable=executable, force_create=force)
460
493
461 @reraise_safe_exceptions
494 @reraise_safe_exceptions
462 def get_hooks_info(self, wire):
495 def get_hooks_info(self, wire):
463 from vcsserver.hook_utils import (
496 from vcsserver.hook_utils import (
464 get_svn_pre_hook_version, get_svn_post_hook_version)
497 get_svn_pre_hook_version, get_svn_post_hook_version)
465 repo_path = wire['path']
498 repo_path = wire['path']
466 return {
499 return {
467 'pre_version': get_svn_pre_hook_version(repo_path),
500 'pre_version': get_svn_pre_hook_version(repo_path),
468 'post_version': get_svn_post_hook_version(repo_path),
501 'post_version': get_svn_post_hook_version(repo_path),
469 }
502 }
470
503
471
504
472 class SvnDiffer(object):
505 class SvnDiffer(object):
473 """
506 """
474 Utility to create diffs based on difflib and the Subversion api
507 Utility to create diffs based on difflib and the Subversion api
475 """
508 """
476
509
477 binary_content = False
510 binary_content = False
478
511
479 def __init__(
512 def __init__(
480 self, repo, src_rev, src_path, tgt_rev, tgt_path,
513 self, repo, src_rev, src_path, tgt_rev, tgt_path,
481 ignore_whitespace, context):
514 ignore_whitespace, context):
482 self.repo = repo
515 self.repo = repo
483 self.ignore_whitespace = ignore_whitespace
516 self.ignore_whitespace = ignore_whitespace
484 self.context = context
517 self.context = context
485
518
486 fsobj = svn.repos.fs(repo)
519 fsobj = svn.repos.fs(repo)
487
520
488 self.tgt_rev = tgt_rev
521 self.tgt_rev = tgt_rev
489 self.tgt_path = tgt_path or ''
522 self.tgt_path = tgt_path or ''
490 self.tgt_root = svn.fs.revision_root(fsobj, tgt_rev)
523 self.tgt_root = svn.fs.revision_root(fsobj, tgt_rev)
491 self.tgt_kind = svn.fs.check_path(self.tgt_root, self.tgt_path)
524 self.tgt_kind = svn.fs.check_path(self.tgt_root, self.tgt_path)
492
525
493 self.src_rev = src_rev
526 self.src_rev = src_rev
494 self.src_path = src_path or self.tgt_path
527 self.src_path = src_path or self.tgt_path
495 self.src_root = svn.fs.revision_root(fsobj, src_rev)
528 self.src_root = svn.fs.revision_root(fsobj, src_rev)
496 self.src_kind = svn.fs.check_path(self.src_root, self.src_path)
529 self.src_kind = svn.fs.check_path(self.src_root, self.src_path)
497
530
498 self._validate()
531 self._validate()
499
532
500 def _validate(self):
533 def _validate(self):
501 if (self.tgt_kind != svn.core.svn_node_none and
534 if (self.tgt_kind != svn.core.svn_node_none and
502 self.src_kind != svn.core.svn_node_none and
535 self.src_kind != svn.core.svn_node_none and
503 self.src_kind != self.tgt_kind):
536 self.src_kind != self.tgt_kind):
504 # TODO: johbo: proper error handling
537 # TODO: johbo: proper error handling
505 raise Exception(
538 raise Exception(
506 "Source and target are not compatible for diff generation. "
539 "Source and target are not compatible for diff generation. "
507 "Source type: %s, target type: %s" %
540 "Source type: %s, target type: %s" %
508 (self.src_kind, self.tgt_kind))
541 (self.src_kind, self.tgt_kind))
509
542
510 def generate_diff(self):
543 def generate_diff(self):
511 buf = StringIO.StringIO()
544 buf = StringIO.StringIO()
512 if self.tgt_kind == svn.core.svn_node_dir:
545 if self.tgt_kind == svn.core.svn_node_dir:
513 self._generate_dir_diff(buf)
546 self._generate_dir_diff(buf)
514 else:
547 else:
515 self._generate_file_diff(buf)
548 self._generate_file_diff(buf)
516 return buf.getvalue()
549 return buf.getvalue()
517
550
518 def _generate_dir_diff(self, buf):
551 def _generate_dir_diff(self, buf):
519 editor = DiffChangeEditor()
552 editor = DiffChangeEditor()
520 editor_ptr, editor_baton = svn.delta.make_editor(editor)
553 editor_ptr, editor_baton = svn.delta.make_editor(editor)
521 svn.repos.dir_delta2(
554 svn.repos.dir_delta2(
522 self.src_root,
555 self.src_root,
523 self.src_path,
556 self.src_path,
524 '', # src_entry
557 '', # src_entry
525 self.tgt_root,
558 self.tgt_root,
526 self.tgt_path,
559 self.tgt_path,
527 editor_ptr, editor_baton,
560 editor_ptr, editor_baton,
528 authorization_callback_allow_all,
561 authorization_callback_allow_all,
529 False, # text_deltas
562 False, # text_deltas
530 svn.core.svn_depth_infinity, # depth
563 svn.core.svn_depth_infinity, # depth
531 False, # entry_props
564 False, # entry_props
532 False, # ignore_ancestry
565 False, # ignore_ancestry
533 )
566 )
534
567
535 for path, __, change in sorted(editor.changes):
568 for path, __, change in sorted(editor.changes):
536 self._generate_node_diff(
569 self._generate_node_diff(
537 buf, change, path, self.tgt_path, path, self.src_path)
570 buf, change, path, self.tgt_path, path, self.src_path)
538
571
539 def _generate_file_diff(self, buf):
572 def _generate_file_diff(self, buf):
540 change = None
573 change = None
541 if self.src_kind == svn.core.svn_node_none:
574 if self.src_kind == svn.core.svn_node_none:
542 change = "add"
575 change = "add"
543 elif self.tgt_kind == svn.core.svn_node_none:
576 elif self.tgt_kind == svn.core.svn_node_none:
544 change = "delete"
577 change = "delete"
545 tgt_base, tgt_path = vcspath.split(self.tgt_path)
578 tgt_base, tgt_path = vcspath.split(self.tgt_path)
546 src_base, src_path = vcspath.split(self.src_path)
579 src_base, src_path = vcspath.split(self.src_path)
547 self._generate_node_diff(
580 self._generate_node_diff(
548 buf, change, tgt_path, tgt_base, src_path, src_base)
581 buf, change, tgt_path, tgt_base, src_path, src_base)
549
582
550 def _generate_node_diff(
583 def _generate_node_diff(
551 self, buf, change, tgt_path, tgt_base, src_path, src_base):
584 self, buf, change, tgt_path, tgt_base, src_path, src_base):
552
585
553 if self.src_rev == self.tgt_rev and tgt_base == src_base:
586 if self.src_rev == self.tgt_rev and tgt_base == src_base:
554 # makes consistent behaviour with git/hg to return empty diff if
587 # makes consistent behaviour with git/hg to return empty diff if
555 # we compare same revisions
588 # we compare same revisions
556 return
589 return
557
590
558 tgt_full_path = vcspath.join(tgt_base, tgt_path)
591 tgt_full_path = vcspath.join(tgt_base, tgt_path)
559 src_full_path = vcspath.join(src_base, src_path)
592 src_full_path = vcspath.join(src_base, src_path)
560
593
561 self.binary_content = False
594 self.binary_content = False
562 mime_type = self._get_mime_type(tgt_full_path)
595 mime_type = self._get_mime_type(tgt_full_path)
563
596
564 if mime_type and not mime_type.startswith('text'):
597 if mime_type and not mime_type.startswith('text'):
565 self.binary_content = True
598 self.binary_content = True
566 buf.write("=" * 67 + '\n')
599 buf.write("=" * 67 + '\n')
567 buf.write("Cannot display: file marked as a binary type.\n")
600 buf.write("Cannot display: file marked as a binary type.\n")
568 buf.write("svn:mime-type = %s\n" % mime_type)
601 buf.write("svn:mime-type = %s\n" % mime_type)
569 buf.write("Index: %s\n" % (tgt_path, ))
602 buf.write("Index: %s\n" % (tgt_path, ))
570 buf.write("=" * 67 + '\n')
603 buf.write("=" * 67 + '\n')
571 buf.write("diff --git a/%(tgt_path)s b/%(tgt_path)s\n" % {
604 buf.write("diff --git a/%(tgt_path)s b/%(tgt_path)s\n" % {
572 'tgt_path': tgt_path})
605 'tgt_path': tgt_path})
573
606
574 if change == 'add':
607 if change == 'add':
575 # TODO: johbo: SVN is missing a zero here compared to git
608 # TODO: johbo: SVN is missing a zero here compared to git
576 buf.write("new file mode 10644\n")
609 buf.write("new file mode 10644\n")
577
610
578 #TODO(marcink): intro to binary detection of svn patches
611 #TODO(marcink): intro to binary detection of svn patches
579 # if self.binary_content:
612 # if self.binary_content:
580 # buf.write('GIT binary patch\n')
613 # buf.write('GIT binary patch\n')
581
614
582 buf.write("--- /dev/null\t(revision 0)\n")
615 buf.write("--- /dev/null\t(revision 0)\n")
583 src_lines = []
616 src_lines = []
584 else:
617 else:
585 if change == 'delete':
618 if change == 'delete':
586 buf.write("deleted file mode 10644\n")
619 buf.write("deleted file mode 10644\n")
587
620
588 #TODO(marcink): intro to binary detection of svn patches
621 #TODO(marcink): intro to binary detection of svn patches
589 # if self.binary_content:
622 # if self.binary_content:
590 # buf.write('GIT binary patch\n')
623 # buf.write('GIT binary patch\n')
591
624
592 buf.write("--- a/%s\t(revision %s)\n" % (
625 buf.write("--- a/%s\t(revision %s)\n" % (
593 src_path, self.src_rev))
626 src_path, self.src_rev))
594 src_lines = self._svn_readlines(self.src_root, src_full_path)
627 src_lines = self._svn_readlines(self.src_root, src_full_path)
595
628
596 if change == 'delete':
629 if change == 'delete':
597 buf.write("+++ /dev/null\t(revision %s)\n" % (self.tgt_rev, ))
630 buf.write("+++ /dev/null\t(revision %s)\n" % (self.tgt_rev, ))
598 tgt_lines = []
631 tgt_lines = []
599 else:
632 else:
600 buf.write("+++ b/%s\t(revision %s)\n" % (
633 buf.write("+++ b/%s\t(revision %s)\n" % (
601 tgt_path, self.tgt_rev))
634 tgt_path, self.tgt_rev))
602 tgt_lines = self._svn_readlines(self.tgt_root, tgt_full_path)
635 tgt_lines = self._svn_readlines(self.tgt_root, tgt_full_path)
603
636
604 if not self.binary_content:
637 if not self.binary_content:
605 udiff = svn_diff.unified_diff(
638 udiff = svn_diff.unified_diff(
606 src_lines, tgt_lines, context=self.context,
639 src_lines, tgt_lines, context=self.context,
607 ignore_blank_lines=self.ignore_whitespace,
640 ignore_blank_lines=self.ignore_whitespace,
608 ignore_case=False,
641 ignore_case=False,
609 ignore_space_changes=self.ignore_whitespace)
642 ignore_space_changes=self.ignore_whitespace)
610 buf.writelines(udiff)
643 buf.writelines(udiff)
611
644
612 def _get_mime_type(self, path):
645 def _get_mime_type(self, path):
613 try:
646 try:
614 mime_type = svn.fs.node_prop(
647 mime_type = svn.fs.node_prop(
615 self.tgt_root, path, svn.core.SVN_PROP_MIME_TYPE)
648 self.tgt_root, path, svn.core.SVN_PROP_MIME_TYPE)
616 except svn.core.SubversionException:
649 except svn.core.SubversionException:
617 mime_type = svn.fs.node_prop(
650 mime_type = svn.fs.node_prop(
618 self.src_root, path, svn.core.SVN_PROP_MIME_TYPE)
651 self.src_root, path, svn.core.SVN_PROP_MIME_TYPE)
619 return mime_type
652 return mime_type
620
653
621 def _svn_readlines(self, fs_root, node_path):
654 def _svn_readlines(self, fs_root, node_path):
622 if self.binary_content:
655 if self.binary_content:
623 return []
656 return []
624 node_kind = svn.fs.check_path(fs_root, node_path)
657 node_kind = svn.fs.check_path(fs_root, node_path)
625 if node_kind not in (
658 if node_kind not in (
626 svn.core.svn_node_file, svn.core.svn_node_symlink):
659 svn.core.svn_node_file, svn.core.svn_node_symlink):
627 return []
660 return []
628 content = svn.core.Stream(
661 content = svn.core.Stream(
629 svn.fs.file_contents(fs_root, node_path)).read()
662 svn.fs.file_contents(fs_root, node_path)).read()
630 return content.splitlines(True)
663 return content.splitlines(True)
631
664
632
665
633
666
634 class DiffChangeEditor(svn.delta.Editor):
667 class DiffChangeEditor(svn.delta.Editor):
635 """
668 """
636 Records changes between two given revisions
669 Records changes between two given revisions
637 """
670 """
638
671
639 def __init__(self):
672 def __init__(self):
640 self.changes = []
673 self.changes = []
641
674
642 def delete_entry(self, path, revision, parent_baton, pool=None):
675 def delete_entry(self, path, revision, parent_baton, pool=None):
643 self.changes.append((path, None, 'delete'))
676 self.changes.append((path, None, 'delete'))
644
677
645 def add_file(
678 def add_file(
646 self, path, parent_baton, copyfrom_path, copyfrom_revision,
679 self, path, parent_baton, copyfrom_path, copyfrom_revision,
647 file_pool=None):
680 file_pool=None):
648 self.changes.append((path, 'file', 'add'))
681 self.changes.append((path, 'file', 'add'))
649
682
650 def open_file(self, path, parent_baton, base_revision, file_pool=None):
683 def open_file(self, path, parent_baton, base_revision, file_pool=None):
651 self.changes.append((path, 'file', 'change'))
684 self.changes.append((path, 'file', 'change'))
652
685
653
686
654 def authorization_callback_allow_all(root, path, pool):
687 def authorization_callback_allow_all(root, path, pool):
655 return True
688 return True
656
689
657
690
658 class TxnNodeProcessor(object):
691 class TxnNodeProcessor(object):
659 """
692 """
660 Utility to process the change of one node within a transaction root.
693 Utility to process the change of one node within a transaction root.
661
694
662 It encapsulates the knowledge of how to add, update or remove
695 It encapsulates the knowledge of how to add, update or remove
663 a node for a given transaction root. The purpose is to support the method
696 a node for a given transaction root. The purpose is to support the method
664 `SvnRemote.commit`.
697 `SvnRemote.commit`.
665 """
698 """
666
699
667 def __init__(self, node, txn_root):
700 def __init__(self, node, txn_root):
668 assert isinstance(node['path'], str)
701 assert isinstance(node['path'], str)
669
702
670 self.node = node
703 self.node = node
671 self.txn_root = txn_root
704 self.txn_root = txn_root
672
705
673 def update(self):
706 def update(self):
674 self._ensure_parent_dirs()
707 self._ensure_parent_dirs()
675 self._add_file_if_node_does_not_exist()
708 self._add_file_if_node_does_not_exist()
676 self._update_file_content()
709 self._update_file_content()
677 self._update_file_properties()
710 self._update_file_properties()
678
711
679 def remove(self):
712 def remove(self):
680 svn.fs.delete(self.txn_root, self.node['path'])
713 svn.fs.delete(self.txn_root, self.node['path'])
681 # TODO: Clean up directory if empty
714 # TODO: Clean up directory if empty
682
715
683 def _ensure_parent_dirs(self):
716 def _ensure_parent_dirs(self):
684 curdir = vcspath.dirname(self.node['path'])
717 curdir = vcspath.dirname(self.node['path'])
685 dirs_to_create = []
718 dirs_to_create = []
686 while not self._svn_path_exists(curdir):
719 while not self._svn_path_exists(curdir):
687 dirs_to_create.append(curdir)
720 dirs_to_create.append(curdir)
688 curdir = vcspath.dirname(curdir)
721 curdir = vcspath.dirname(curdir)
689
722
690 for curdir in reversed(dirs_to_create):
723 for curdir in reversed(dirs_to_create):
691 log.debug('Creating missing directory "%s"', curdir)
724 log.debug('Creating missing directory "%s"', curdir)
692 svn.fs.make_dir(self.txn_root, curdir)
725 svn.fs.make_dir(self.txn_root, curdir)
693
726
694 def _svn_path_exists(self, path):
727 def _svn_path_exists(self, path):
695 path_status = svn.fs.check_path(self.txn_root, path)
728 path_status = svn.fs.check_path(self.txn_root, path)
696 return path_status != svn.core.svn_node_none
729 return path_status != svn.core.svn_node_none
697
730
698 def _add_file_if_node_does_not_exist(self):
731 def _add_file_if_node_does_not_exist(self):
699 kind = svn.fs.check_path(self.txn_root, self.node['path'])
732 kind = svn.fs.check_path(self.txn_root, self.node['path'])
700 if kind == svn.core.svn_node_none:
733 if kind == svn.core.svn_node_none:
701 svn.fs.make_file(self.txn_root, self.node['path'])
734 svn.fs.make_file(self.txn_root, self.node['path'])
702
735
703 def _update_file_content(self):
736 def _update_file_content(self):
704 assert isinstance(self.node['content'], str)
737 assert isinstance(self.node['content'], str)
705 handler, baton = svn.fs.apply_textdelta(
738 handler, baton = svn.fs.apply_textdelta(
706 self.txn_root, self.node['path'], None, None)
739 self.txn_root, self.node['path'], None, None)
707 svn.delta.svn_txdelta_send_string(self.node['content'], handler, baton)
740 svn.delta.svn_txdelta_send_string(self.node['content'], handler, baton)
708
741
709 def _update_file_properties(self):
742 def _update_file_properties(self):
710 properties = self.node.get('properties', {})
743 properties = self.node.get('properties', {})
711 for key, value in properties.iteritems():
744 for key, value in properties.iteritems():
712 svn.fs.change_node_prop(
745 svn.fs.change_node_prop(
713 self.txn_root, self.node['path'], key, value)
746 self.txn_root, self.node['path'], key, value)
714
747
715
748
716 def apr_time_t(timestamp):
749 def apr_time_t(timestamp):
717 """
750 """
718 Convert a Python timestamp into APR timestamp type apr_time_t
751 Convert a Python timestamp into APR timestamp type apr_time_t
719 """
752 """
720 return timestamp * 1E6
753 return timestamp * 1E6
721
754
722
755
723 def svn_opt_revision_value_t(num):
756 def svn_opt_revision_value_t(num):
724 """
757 """
725 Put `num` into a `svn_opt_revision_value_t` structure.
758 Put `num` into a `svn_opt_revision_value_t` structure.
726 """
759 """
727 value = svn.core.svn_opt_revision_value_t()
760 value = svn.core.svn_opt_revision_value_t()
728 value.number = num
761 value.number = num
729 revision = svn.core.svn_opt_revision_t()
762 revision = svn.core.svn_opt_revision_t()
730 revision.kind = svn.core.svn_opt_revision_number
763 revision.kind = svn.core.svn_opt_revision_number
731 revision.value = value
764 revision.value = value
732 return revision
765 return revision
General Comments 0
You need to be logged in to leave comments. Login now