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