345
292
recurse, switch_url, edit, edit_baton, pool))
347
294
@convert_svn_error
349
def get_log(self, path, from_revnum, to_revnum, limit, discover_changed_paths,
350
strict_node_history, revprops, rcvr, pool=None):
351
self.mutter('svn log %r:%r %r' % (from_revnum, to_revnum, path))
295
def change_rev_prop(self, revnum, name, value, pool=None):
296
self.mutter('svn revprop -r%d --set %s=%s' % (revnum, name, value))
297
svn.ra.change_rev_prop(self._ra, revnum, name, value)
301
def get_lock(self, path):
302
return svn.ra.get_lock(self._ra, path)
306
def unlock(self, locks, break_lock=False):
307
def lock_cb(baton, path, do_lock, lock, ra_err, pool):
309
return svn.ra.unlock(self._ra, locks, break_lock, lock_cb)
313
def get_dir(self, path, revnum, pool=None, kind=False):
314
self.mutter("svn ls -r %d '%r'" % (revnum, path))
315
assert len(path) == 0 or path[0] != "/"
316
# ra_dav backends fail with strange errors if the path starts with a
317
# slash while other backends don't.
318
if hasattr(svn.ra, 'get_dir2'):
321
fields += svn.core.SVN_DIRENT_KIND
322
return svn.ra.get_dir2(self._ra, path, revnum, fields)
324
return svn.ra.get_dir(self._ra, path, revnum)
328
def check_path(self, path, revnum):
329
assert len(path) == 0 or path[0] != "/"
330
self.mutter("svn check_path -r%d %s" % (revnum, path))
331
return svn.ra.check_path(self._ra, path.encode('utf-8'), revnum)
335
def mkdir(self, relpath, mode=None):
336
assert len(relpath) == 0 or relpath[0] != "/"
337
path = urlutils.join(self.url, relpath)
339
svn.client.mkdir([path.encode("utf-8")], self._client)
340
except SubversionException, (msg, num):
341
if num == svn.core.SVN_ERR_FS_NOT_FOUND:
342
raise NoSuchFile(path)
343
if num == svn.core.SVN_ERR_FS_ALREADY_EXISTS:
344
raise FileExists(path)
348
def replay(self, revision, low_water_mark, send_deltas, editor, pool=None):
349
self.mutter('svn replay -r%r:%r' % (low_water_mark, revision))
351
edit, edit_baton = self._make_editor(editor, pool)
352
svn.ra.replay(self._ra, revision, low_water_mark, send_deltas,
353
edit, edit_baton, pool)
356
def do_update(self, revnum, recurse, editor, pool=None):
357
self.mutter('svn update -r %r' % revnum)
359
edit, edit_baton = self._make_editor(editor, pool)
360
return self.Reporter(self, svn.ra.do_update(self._ra, revnum, "",
361
recurse, edit, edit_baton, pool))
364
def has_capability(self, cap):
365
return svn.ra.has_capability(self._ra, cap)
368
def revprop_list(self, revnum, pool=None):
369
self.mutter('svn revprop-list -r %r' % revnum)
370
return svn.ra.rev_proplist(self._ra, revnum, pool)
373
def get_commit_editor(self, revprops, done_cb, lock_token, keep_locks):
376
if hasattr(svn.ra, 'get_commit_editor3'):
377
editor = svn.ra.get_commit_editor3(self._ra, revprops, done_cb,
378
lock_token, keep_locks)
379
elif revprops.keys() != [svn.core.SVN_PROP_REVISION_LOG]:
380
raise NotImplementedError()
382
editor = svn.ra.get_commit_editor2(self._ra,
383
revprops[svn.core.SVN_PROP_REVISION_LOG],
384
done_cb, lock_token, keep_locks)
386
return Editor(self, editor)
391
class SvnLock(object):
392
def __init__(self, transport, tokens):
393
self._tokens = tokens
394
self._transport = transport
397
self.transport.unlock(self.locks)
401
def lock_write(self, path_revs, comment=None, steal_lock=False):
403
def lock_cb(baton, path, do_lock, lock, ra_err, pool):
405
svn.ra.lock(self._ra, path_revs, comment, steal_lock, lock_cb)
406
return SvnLock(self, tokens)
410
def get_log(self, paths, from_revnum, to_revnum, limit,
411
discover_changed_paths, strict_node_history, revprops, rcvr,
415
self.mutter('svn log %r:%r %r' % (from_revnum, to_revnum, paths))
352
416
if hasattr(svn.ra, 'get_log2'):
353
return svn.ra.get_log2(self._ra, [self._request_path(path)],
354
from_revnum, to_revnum, limit, discover_changed_paths,
355
strict_node_history, False,
417
return svn.ra.get_log2(self._ra, paths,
418
from_revnum, to_revnum, limit,
419
discover_changed_paths, strict_node_history, False,
356
420
revprops, rcvr, pool)
422
class LogEntry(object):
359
423
def __init__(self, changed_paths, rev, author, date, message):
360
424
self.changed_paths = changed_paths
361
425
self.revprops = {}
373
437
def rcvr_convert(orig_paths, rev, author, date, message, pool):
374
438
rcvr(LogEntry(orig_paths, rev, author, date, message), pool)
376
return svn.ra.get_log(self._ra, [self._request_path(path)],
440
return svn.ra.get_log(self._ra, paths,
377
441
from_revnum, to_revnum, limit, discover_changed_paths,
378
442
strict_node_history, rcvr_convert, pool)
380
def _open_real_transport(self):
381
if self._backing_url != self.svn_url:
382
self.reparent(self.base)
383
assert self._backing_url == self.svn_url
385
def reparent_root(self):
386
if self._is_http_transport():
387
self.svn_url = self.get_svn_repos_root()
388
self.base = self.get_repos_root()
390
self.reparent(self.get_repos_root())
393
def change_rev_prop(self, revnum, name, value, pool=None):
394
self.mutter('svn revprop -r%d --set %s=%s' % (revnum, name, value))
395
svn.ra.change_rev_prop(self._ra, revnum, name, value)
397
444
@convert_svn_error
399
446
def reparent(self, url):
400
url = url.rstrip("/")
402
self.svn_url = bzr_to_svn_url(url)
403
if self.svn_url == self._backing_url:
405
449
if hasattr(svn.ra, 'reparent'):
406
450
self.mutter('svn reparent %r' % url)
407
svn.ra.reparent(self._ra, self.svn_url, self.pool)
451
svn.ra.reparent(self._ra, url)
409
self.mutter('svn reparent (reconnect) %r' % url)
410
self._ra = svn.client.open_ra_session(self.svn_url.encode('utf8'),
411
self._client, self.pool)
412
self._backing_url = self.svn_url
454
raise NotImplementedError(self.reparent)
457
class ConnectionPool(object):
458
"""Collection of connections to a Subversion repository."""
460
self.connections = set()
463
# Check if there is an existing connection we can use
464
for c in self.connections:
466
self.connections.remove(c)
468
# Nothing available? Just pick an existing one and reparent:
469
if len(self.connections) == 0:
470
return Connection(url)
471
c = self.connections.pop()
475
except NotImplementedError:
476
self.connections.add(c)
477
return Connection(url)
479
self.connections.add(c)
482
def add(self, connection):
483
self.connections.add(connection)
486
class SvnRaTransport(Transport):
487
"""Fake transport for Subversion-related namespaces.
489
This implements just as much of Transport as is necessary
414
491
@convert_svn_error
492
def __init__(self, url="", _backing_url=None, pool=None):
495
self.svn_url = bzr_to_svn_url(url)
496
# _backing_url is an evil hack so the root directory of a repository
497
# can be accessed on some HTTP repositories.
498
if _backing_url is None:
499
_backing_url = self.svn_url
500
self._backing_url = _backing_url.rstrip("/")
501
Transport.__init__(self, bzr_url)
504
self.connections = ConnectionPool()
506
self.connections = pool
508
# Make sure that the URL is valid by connecting to it.
509
self.connections.add(self.connections.get(self._backing_url))
511
from bzrlib.plugins.svn import lazy_check_versions
512
lazy_check_versions()
514
def get_connection(self):
515
return self.connections.get(self._backing_url)
517
def add_connection(self, conn):
518
self.connections.add(conn)
520
def has(self, relpath):
521
"""See Transport.has()."""
522
# TODO: Raise TransportNotPossible here instead and
523
# catch it in bzrdir.py
526
def get(self, relpath):
527
"""See Transport.get()."""
528
# TODO: Raise TransportNotPossible here instead and
529
# catch it in bzrdir.py
530
raise NoSuchFile(path=relpath)
532
def stat(self, relpath):
533
"""See Transport.stat()."""
534
raise TransportNotPossible('stat not supported on Subversion')
537
conn = self.get_connection()
539
return conn.get_uuid()
541
self.add_connection(conn)
543
def get_repos_root(self):
544
root = self.get_svn_repos_root()
545
if (self.base.startswith("svn+http:") or
546
self.base.startswith("svn+https:")):
547
return "svn+%s" % root
550
def get_svn_repos_root(self):
551
conn = self.get_connection()
553
return conn.get_repos_root()
555
self.add_connection(conn)
557
def get_latest_revnum(self):
558
conn = self.get_connection()
560
return conn.get_latest_revnum()
562
self.add_connection(conn)
564
def do_switch(self, switch_rev, recurse, switch_url, editor, pool=None):
565
conn = self._open_real_transport()
567
return conn.do_switch(switch_rev, recurse, switch_url, editor, pool)
569
self.add_connection(conn)
571
def iter_log(self, paths, from_revnum, to_revnum, limit, discover_changed_paths,
572
strict_node_history, revprops):
574
assert paths is None or isinstance(paths, list)
575
assert paths is None or all([isinstance(x, str) for x in paths])
576
assert isinstance(from_revnum, int) and isinstance(to_revnum, int)
577
assert isinstance(limit, int)
578
from threading import Thread, Semaphore
580
class logfetcher(Thread):
581
def __init__(self, transport, *args, **kwargs):
582
Thread.__init__(self)
584
self.transport = transport
589
self.semaphore = Semaphore(0)
592
self.semaphore.acquire()
593
ret = self.pending.pop(0)
595
self.transport.add_connection(self.conn)
596
if isinstance(ret, Exception):
597
self.transport.add_connection(self.conn)
602
def rcvr(log_entry, pool):
603
self.pending.append((log_entry.changed_paths, log_entry.revision, log_entry.revprops))
604
self.semaphore.release()
605
self.conn = self.transport.get_connection()
607
self.conn.get_log(rcvr=rcvr, *self.args, **self.kwargs)
608
self.pending.append(None)
610
self.pending.append(e)
611
self.semaphore.release()
616
newpaths = [self._request_path(path) for path in paths]
618
fetcher = logfetcher(self, paths, from_revnum, to_revnum, limit, discover_changed_paths, strict_node_history, revprops)
620
return iter(fetcher.next, None)
622
def get_log(self, paths, from_revnum, to_revnum, limit, discover_changed_paths,
623
strict_node_history, revprops, rcvr, pool=None):
624
assert paths is None or isinstance(paths, list), "Invalid paths"
625
assert paths is None or all([isinstance(x, str) for x in paths])
630
newpaths = [self._request_path(path) for path in paths]
632
conn = self.get_connection()
634
return conn.get_log(newpaths,
635
from_revnum, to_revnum,
636
limit, discover_changed_paths, strict_node_history,
637
revprops, rcvr, pool)
639
self.add_connection(conn)
641
def _open_real_transport(self):
642
if self._backing_url != self.svn_url:
643
return self.connections.get(self.svn_url)
644
return self.get_connection()
646
def change_rev_prop(self, revnum, name, value, pool=None):
647
conn = self.get_connection()
649
return conn.change_rev_prop(revnum, name, value, pool)
651
self.add_connection(conn)
416
653
def get_dir(self, path, revnum, pool=None, kind=False):
417
self.mutter("svn ls -r %d '%r'" % (revnum, path))
418
assert len(path) == 0 or path[0] != "/"
419
654
path = self._request_path(path)
420
# ra_dav backends fail with strange errors if the path starts with a
421
# slash while other backends don't.
422
if hasattr(svn.ra, 'get_dir2'):
425
fields += svn.core.SVN_DIRENT_KIND
426
return svn.ra.get_dir2(self._ra, path, revnum, fields)
428
return svn.ra.get_dir(self._ra, path, revnum)
655
conn = self.get_connection()
657
return conn.get_dir(path, revnum, pool, kind)
659
self.add_connection(conn)
661
def mutter(self, text):
662
if 'transport' in debug.debug_flags:
430
665
def _request_path(self, relpath):
431
666
if self._backing_url == self.svn_url:
436
671
self.mutter('request path %r -> %r' % (relpath, newrelpath))
437
672
return newrelpath
440
674
def list_dir(self, relpath):
441
675
assert len(relpath) == 0 or relpath[0] != "/"
442
676
if relpath == ".":
445
(dirents, _, _) = self.get_dir(self._request_path(relpath),
446
self.get_latest_revnum())
679
(dirents, _, _) = self.get_dir(relpath, self.get_latest_revnum())
447
680
except SubversionException, (msg, num):
448
681
if num == svn.core.SVN_ERR_FS_NOT_DIRECTORY:
449
682
raise NoSuchFile(relpath)
451
684
return dirents.keys()
455
def get_lock(self, path):
456
return svn.ra.get_lock(self._ra, path)
459
def __init__(self, transport, tokens):
460
self._tokens = tokens
461
self._transport = transport
464
self.transport.unlock(self.locks)
468
def unlock(self, locks, break_lock=False):
469
def lock_cb(baton, path, do_lock, lock, ra_err, pool):
471
return svn.ra.unlock(self._ra, locks, break_lock, lock_cb)
475
def lock_write(self, path_revs, comment=None, steal_lock=False):
476
return self.PhonyLock() # FIXME
478
def lock_cb(baton, path, do_lock, lock, ra_err, pool):
480
svn.ra.lock(self._ra, path_revs, comment, steal_lock, lock_cb)
481
return SvnLock(self, tokens)
485
def check_path(self, path, revnum, *args, **kwargs):
486
assert len(path) == 0 or path[0] != "/"
686
def check_path(self, path, revnum):
487
687
path = self._request_path(path)
488
self.mutter("svn check_path -r%d %s" % (revnum, path))
489
return svn.ra.check_path(self._ra, path.encode('utf-8'), revnum, *args, **kwargs)
688
conn = self.get_connection()
690
return conn.check_path(path, revnum)
692
self.add_connection(conn)
493
694
def mkdir(self, relpath, mode=None):
494
assert len(relpath) == 0 or relpath[0] != "/"
495
path = urlutils.join(self.svn_url, relpath)
695
conn = self.get_connection()
497
svn.client.mkdir([path.encode("utf-8")], self._client)
498
except SubversionException, (msg, num):
499
if num == svn.core.SVN_ERR_FS_NOT_FOUND:
500
raise NoSuchFile(path)
501
if num == svn.core.SVN_ERR_FS_ALREADY_EXISTS:
502
raise FileExists(path)
697
return conn.mkdir(relpath, mode)
699
self.add_connection(conn)
506
701
def replay(self, revision, low_water_mark, send_deltas, editor, pool=None):
507
self._open_real_transport()
508
self.mutter('svn replay -r%r:%r' % (low_water_mark, revision))
510
edit, edit_baton = self._make_editor(editor, pool)
511
svn.ra.replay(self._ra, revision, low_water_mark, send_deltas,
512
edit, edit_baton, pool)
702
conn = self._open_real_transport()
704
return conn.replay(revision, low_water_mark,
705
send_deltas, editor, pool)
707
self.add_connection(conn)
515
709
def do_update(self, revnum, recurse, editor, pool=None):
516
self._open_real_transport()
517
self.mutter('svn update -r %r' % revnum)
519
edit, edit_baton = self._make_editor(editor, pool)
520
return self.Reporter(self, svn.ra.do_update(self._ra, revnum, "",
521
recurse, edit, edit_baton, pool))
710
conn = self._open_real_transport()
712
return conn.do_update(revnum, recurse, editor, pool)
714
self.add_connection(conn)
524
716
def has_capability(self, cap):
525
return svn.ra.has_capability(self._ra, cap)
717
conn = self.get_connection()
719
return conn.has_capability(cap)
721
self.add_connection(conn)
528
723
def revprop_list(self, revnum, pool=None):
529
self.mutter('svn revprop-list -r %r' % revnum)
530
return svn.ra.rev_proplist(self._ra, revnum, pool)
724
conn = self.get_connection()
726
return conn.revprop_list(revnum, pool)
728
self.add_connection(conn)
533
730
def get_commit_editor(self, revprops, done_cb, lock_token, keep_locks):
534
self._open_real_transport()
731
conn = self._open_real_transport()
537
if hasattr(svn.ra, 'get_commit_editor3'):
538
editor = svn.ra.get_commit_editor3(self._ra, revprops, done_cb,
539
lock_token, keep_locks)
540
elif revprops.keys() != [svn.core.SVN_PROP_REVISION_LOG]:
541
raise NotImplementedError()
543
editor = svn.ra.get_commit_editor2(self._ra,
544
revprops[svn.core.SVN_PROP_REVISION_LOG],
545
done_cb, lock_token, keep_locks)
547
return Editor(self, editor)
733
return conn.get_commit_editor(revprops, done_cb,
734
lock_token, keep_locks)
736
self.add_connection(conn)
552
738
def listable(self):
553
739
"""See Transport.listable().