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