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