32
33
from bzrlib.branch import BranchReferenceFormat
33
34
from bzrlib.bzrdir import BzrDir, RemoteBzrDirFormat
34
35
from bzrlib.config import BranchConfig, TreeConfig
35
36
from bzrlib.decorators import needs_read_lock, needs_write_lock
36
from bzrlib.errors import NoSuchRevision
37
from bzrlib.errors import (
37
41
from bzrlib.lockable_files import LockableFiles
38
42
from bzrlib.pack import ContainerPushParser
39
43
from bzrlib.smart import client, vfs
40
44
from bzrlib.symbol_versioning import (
44
from bzrlib.revision import NULL_REVISION
48
from bzrlib.revision import ensure_null, NULL_REVISION
45
49
from bzrlib.trace import mutter, note, warning
47
52
# Note: RemoteBzrDirFormat is in bzrdir.py
49
54
class RemoteBzrDir(BzrDir):
118
122
def get_branch_reference(self):
119
123
"""See BzrDir.get_branch_reference()."""
120
124
path = self._path_for_remote_call(self._client)
121
response = self._client.call('BzrDir.open_branch', path)
126
response = self._client.call('BzrDir.open_branch', path)
127
except errors.ErrorFromSmartServer, err:
128
if err.error_tuple == ('nobranch',):
129
raise errors.NotBranchError(path=self.root_transport.base)
122
131
if response[0] == 'ok':
123
132
if response[1] == '':
124
133
# branch at this location.
136
143
return None, self.open_branch()
138
145
def open_branch(self, _unsupported=False):
139
assert _unsupported == False, 'unsupported flag support not implemented yet.'
147
raise NotImplementedError('unsupported flag support not implemented yet.')
140
148
reference_url = self.get_branch_reference()
141
149
if reference_url is None:
142
150
# branch at this location.
149
157
def open_repository(self):
150
158
path = self._path_for_remote_call(self._client)
151
159
verb = 'BzrDir.find_repositoryV2'
152
response = self._client.call(verb, path)
153
if (response == ('error', "Generic bzr smart protocol error: "
154
"bad request '%s'" % verb) or
155
response == ('error', "Generic bzr smart protocol error: "
156
"bad request u'%s'" % verb)):
157
verb = 'BzrDir.find_repository'
158
response = self._client.call(verb, path)
159
assert response[0] in ('ok', 'norepository'), \
160
'unexpected response code %s' % (response,)
161
if response[0] == 'norepository':
162
raise errors.NoRepositoryPresent(self)
162
response = self._client.call(verb, path)
163
except errors.UnknownSmartMethod:
164
verb = 'BzrDir.find_repository'
165
response = self._client.call(verb, path)
166
except errors.ErrorFromSmartServer, err:
167
if err.error_verb == 'norepository':
168
raise errors.NoRepositoryPresent(self)
170
if response[0] != 'ok':
171
raise errors.UnexpectedSmartServerResponse(response)
163
172
if verb == 'BzrDir.find_repository':
164
173
# servers that don't support the V2 method don't support external
165
174
# references either.
166
175
response = response + ('no', )
167
assert len(response) == 5, 'incorrect response length %s' % (response,)
176
if not (len(response) == 5):
177
raise SmartProtocolError('incorrect response length %s' % (response,))
168
178
if response[1] == '':
169
179
format = RemoteRepositoryFormat()
170
180
format.rich_root_data = (response[2] == 'yes')
228
238
_matchingbzrdir = RemoteBzrDirFormat
230
240
def initialize(self, a_bzrdir, shared=False):
231
assert isinstance(a_bzrdir, RemoteBzrDir), \
232
'%r is not a RemoteBzrDir' % (a_bzrdir,)
241
if not isinstance(a_bzrdir, RemoteBzrDir):
242
raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
233
243
return a_bzrdir.create_repository(shared=shared)
235
245
def open(self, a_bzrdir):
236
assert isinstance(a_bzrdir, RemoteBzrDir)
246
if not isinstance(a_bzrdir, RemoteBzrDir):
247
raise AssertionError('%r is not a RemoteBzrDir' % (a_bzrdir,))
237
248
return a_bzrdir.open_repository()
239
250
def get_format_description(self):
360
371
self._ensure_real()
361
372
return self._real_repository._generate_text_key_index()
374
@symbol_versioning.deprecated_method(symbol_versioning.one_four)
363
375
def get_revision_graph(self, revision_id=None):
364
376
"""See Repository.get_revision_graph()."""
377
return self._get_revision_graph(revision_id)
379
def _get_revision_graph(self, revision_id):
380
"""Private method for using with old (< 1.2) servers to fallback."""
365
381
if revision_id is None:
367
383
elif revision.is_null(revision_id):
370
386
path = self.bzrdir._path_for_remote_call(self._client)
371
assert type(revision_id) is str
372
response = self._client.call_expecting_body(
373
'Repository.get_revision_graph', path, revision_id)
374
if response[0][0] not in ['ok', 'nosuchrevision']:
375
raise errors.UnexpectedSmartServerResponse(response[0])
376
if response[0][0] == 'ok':
377
coded = response[1].read_body_bytes()
379
# no revisions in this repository!
381
lines = coded.split('\n')
384
d = tuple(line.split())
385
revision_graph[d[0]] = d[1:]
387
return revision_graph
389
response_body = response[1].read_body_bytes()
390
assert response_body == ''
391
raise NoSuchRevision(self, revision_id)
388
response = self._client.call_expecting_body(
389
'Repository.get_revision_graph', path, revision_id)
390
except errors.ErrorFromSmartServer, err:
391
if err.error_verb == 'nosuchrevision':
392
raise NoSuchRevision(self, revision_id)
394
response_tuple, response_handler = response
395
if response_tuple[0] != 'ok':
396
raise errors.UnexpectedSmartServerResponse(response_tuple)
397
coded = response_handler.read_body_bytes()
399
# no revisions in this repository!
401
lines = coded.split('\n')
404
d = tuple(line.split())
405
revision_graph[d[0]] = d[1:]
407
return revision_graph
393
409
def has_revision(self, revision_id):
394
410
"""See Repository.has_revision()."""
396
412
# The null revision is always present.
398
414
path = self.bzrdir._path_for_remote_call(self._client)
399
response = self._client.call('Repository.has_revision', path, revision_id)
400
assert response[0] in ('yes', 'no'), 'unexpected response code %s' % (response,)
415
response = self._client.call(
416
'Repository.has_revision', path, revision_id)
417
if response[0] not in ('yes', 'no'):
418
raise errors.UnexpectedSmartServerResponse(response)
401
419
return response[0] == 'yes'
403
421
def has_revisions(self, revision_ids):
434
452
fmt_committers = 'no'
436
454
fmt_committers = 'yes'
437
response = self._client.call_expecting_body(
455
response_tuple, response_handler = self._client.call_expecting_body(
438
456
'Repository.gather_stats', path, fmt_revid, fmt_committers)
439
assert response[0][0] == 'ok', \
440
'unexpected response code %s' % (response[0],)
457
if response_tuple[0] != 'ok':
458
raise errors.UnexpectedSmartServerResponse(response_tuple)
442
body = response[1].read_body_bytes()
460
body = response_handler.read_body_bytes()
444
462
for line in body.split('\n'):
480
498
"""See Repository.is_shared()."""
481
499
path = self.bzrdir._path_for_remote_call(self._client)
482
500
response = self._client.call('Repository.is_shared', path)
483
assert response[0] in ('yes', 'no'), 'unexpected response code %s' % (response,)
501
if response[0] not in ('yes', 'no'):
502
raise SmartProtocolError('unexpected response code %s' % (response,))
484
503
return response[0] == 'yes'
486
505
def is_write_locked(self):
503
522
path = self.bzrdir._path_for_remote_call(self._client)
504
523
if token is None:
506
response = self._client.call('Repository.lock_write', path, token)
526
response = self._client.call('Repository.lock_write', path, token)
527
except errors.ErrorFromSmartServer, err:
528
if err.error_verb == 'LockContention':
529
raise errors.LockContention('(remote lock)')
530
elif err.error_verb == 'UnlockableTransport':
531
raise errors.UnlockableTransport(self.bzrdir.root_transport)
532
elif err.error_verb == 'LockFailed':
533
raise errors.LockFailed(err.error_args[0], err.error_args[1])
507
536
if response[0] == 'ok':
508
537
ok, token = response
510
elif response[0] == 'LockContention':
511
raise errors.LockContention('(remote lock)')
512
elif response[0] == 'UnlockableTransport':
513
raise errors.UnlockableTransport(self.bzrdir.root_transport)
514
elif response[0] == 'LockFailed':
515
raise errors.LockFailed(response[1], response[2])
517
540
raise errors.UnexpectedSmartServerResponse(response)
582
606
# with no token the remote repository is not persistently locked.
584
response = self._client.call('Repository.unlock', path, token)
609
response = self._client.call('Repository.unlock', path, token)
610
except errors.ErrorFromSmartServer, err:
611
if err.error_verb == 'TokenMismatch':
612
raise errors.TokenMismatch(token, '(remote token)')
585
614
if response == ('ok',):
587
elif response[0] == 'TokenMismatch':
588
raise errors.TokenMismatch(token, '(remote token)')
590
617
raise errors.UnexpectedSmartServerResponse(response)
631
658
path = self.bzrdir._path_for_remote_call(self._client)
632
response, protocol = self._client.call_expecting_body(
633
'Repository.tarball', path, compression)
660
response, protocol = self._client.call_expecting_body(
661
'Repository.tarball', path, compression)
662
except errors.UnknownSmartMethod:
663
protocol.cancel_read_body()
634
665
if response[0] == 'ok':
635
666
# Extract the tarball and return it
636
667
t = tempfile.NamedTemporaryFile()
638
669
t.write(protocol.read_body_bytes())
641
if (response == ('error', "Generic bzr smart protocol error: "
642
"bad request 'Repository.tarball'") or
643
response == ('error', "Generic bzr smart protocol error: "
644
"bad request u'Repository.tarball'")):
645
protocol.cancel_read_body()
647
672
raise errors.UnexpectedSmartServerResponse(response)
649
674
def sprout(self, to_bzrdir, revision_id=None):
713
738
return self._real_repository.clone(a_bzrdir, revision_id=revision_id)
715
740
def make_working_trees(self):
716
"""RemoteRepositories never create working trees by default."""
741
"""See Repository.make_working_trees"""
743
return self._real_repository.make_working_trees()
719
745
def revision_ids_to_search_result(self, result_set):
720
746
"""Convert a set of revision ids to a graph SearchResult."""
805
831
ancestry.update(parent_map)
806
832
present_keys = [k for k in keys if k in ancestry]
807
833
if 'hpss' in debug.debug_flags:
808
self._requested_parents.update(present_keys)
809
mutter('Current RemoteRepository graph hit rate: %d%%',
810
100.0 * len(self._requested_parents) / len(ancestry))
834
if self._requested_parents is not None and len(ancestry) != 0:
835
self._requested_parents.update(present_keys)
836
mutter('Current RemoteRepository graph hit rate: %d%%',
837
100.0 * len(self._requested_parents) / len(ancestry))
811
838
return dict((k, ancestry[k]) for k in present_keys)
813
def _response_is_unknown_method(self, response, verb):
814
"""Return True if response is an unknonwn method response to verb.
816
:param response: The response from a smart client call_expecting_body
818
:param verb: The verb used in that call.
819
:return: True if an unknown method was encountered.
821
# This might live better on
822
# bzrlib.smart.protocol.SmartClientRequestProtocolOne
823
if (response[0] == ('error', "Generic bzr smart protocol error: "
824
"bad request '%s'" % verb) or
825
response[0] == ('error', "Generic bzr smart protocol error: "
826
"bad request u'%s'" % verb)):
827
response[1].cancel_read_body()
831
840
def _get_parent_map(self, keys):
832
841
"""Helper for get_parent_map that performs the RPC."""
833
medium = self._client.get_smart_medium()
842
medium = self._client._medium
834
843
if not medium._remote_is_at_least_1_2:
835
844
# We already found out that the server can't understand
836
845
# Repository.get_parent_map requests, so just fetch the whole
838
return self.get_revision_graph()
847
# XXX: Note that this will issue a deprecation warning. This is ok
848
# :- its because we're working with a deprecated server anyway, and
849
# the user will almost certainly have seen a warning about the
850
# server version already.
851
rg = self.get_revision_graph()
852
# There is an api discrepency between get_parent_map and
853
# get_revision_graph. Specifically, a "key:()" pair in
854
# get_revision_graph just means a node has no parents. For
855
# "get_parent_map" it means the node is a ghost. So fix up the
856
# graph to correct this.
857
# https://bugs.launchpad.net/bzr/+bug/214894
858
# There is one other "bug" which is that ghosts in
859
# get_revision_graph() are not returned at all. But we won't worry
860
# about that for now.
861
for node_id, parent_ids in rg.iteritems():
863
rg[node_id] = (NULL_REVISION,)
864
rg[NULL_REVISION] = ()
869
raise ValueError('get_parent_map(None) is not valid')
841
870
if NULL_REVISION in keys:
842
871
keys.discard(NULL_REVISION)
843
872
found_parents = {NULL_REVISION:()}
871
900
body = self._serialise_search_recipe(recipe)
872
901
path = self.bzrdir._path_for_remote_call(self._client)
874
assert type(key) is str
903
if type(key) is not str:
905
"key %r not a plain string" % (key,))
875
906
verb = 'Repository.get_parent_map'
876
907
args = (path,) + tuple(keys)
877
response = self._client.call_with_body_bytes_expecting_body(
878
verb, args, self._serialise_search_recipe(recipe))
879
if self._response_is_unknown_method(response, verb):
909
response = self._client.call_with_body_bytes_expecting_body(
910
verb, args, self._serialise_search_recipe(recipe))
911
except errors.UnknownSmartMethod:
880
912
# Server does not support this method, so get the whole graph.
881
913
# Worse, we have to force a disconnection, because the server now
882
914
# doesn't realise it has a body on the wire to consume, so the
888
920
# To avoid having to disconnect repeatedly, we keep track of the
889
921
# fact the server doesn't understand remote methods added in 1.2.
890
922
medium._remote_is_at_least_1_2 = False
891
return self.get_revision_graph()
892
elif response[0][0] not in ['ok']:
893
reponse[1].cancel_read_body()
894
raise errors.UnexpectedSmartServerResponse(response[0])
895
if response[0][0] == 'ok':
896
coded = bz2.decompress(response[1].read_body_bytes())
923
return self.get_revision_graph(None)
924
response_tuple, response_handler = response
925
if response_tuple[0] not in ['ok']:
926
response_handler.cancel_read_body()
927
raise errors.UnexpectedSmartServerResponse(response_tuple)
928
if response_tuple[0] == 'ok':
929
coded = bz2.decompress(response_handler.read_body_bytes())
898
931
# no revisions found
1044
1079
return self._real_repository.has_signature_for_revision_id(revision_id)
1046
1081
def get_data_stream_for_search(self, search):
1047
medium = self._client.get_smart_medium()
1082
medium = self._client._medium
1048
1083
if not medium._remote_is_at_least_1_2:
1049
1084
self._ensure_real()
1050
1085
return self._real_repository.get_data_stream_for_search(search)
1051
1086
REQUEST_NAME = 'Repository.stream_revisions_chunked'
1052
1087
path = self.bzrdir._path_for_remote_call(self._client)
1053
1088
body = self._serialise_search_recipe(search.get_recipe())
1054
response, protocol = self._client.call_with_body_bytes_expecting_body(
1055
REQUEST_NAME, (path,), body)
1057
if self._response_is_unknown_method((response, protocol), REQUEST_NAME):
1090
result = self._client.call_with_body_bytes_expecting_body(
1091
REQUEST_NAME, (path,), body)
1092
response, protocol = result
1093
except errors.UnknownSmartMethod:
1058
1094
# Server does not support this method, so fall back to VFS.
1059
1095
# Worse, we have to force a disconnection, because the server now
1060
1096
# doesn't realise it has a body on the wire to consume, so the
1150
1186
self._dir_mode = None
1151
1187
self._file_mode = None
1153
def get(self, path):
1154
"""'get' a remote path as per the LockableFiles interface.
1156
:param path: the file to 'get'. If this is 'branch.conf', we do not
1157
just retrieve a file, instead we ask the smart server to generate
1158
a configuration for us - which is retrieved as an INI file.
1160
if path == 'branch.conf':
1161
path = self.bzrdir._path_for_remote_call(self._client)
1162
response = self._client.call_expecting_body(
1163
'Branch.get_config_file', path)
1164
assert response[0][0] == 'ok', \
1165
'unexpected response code %s' % (response[0],)
1166
return StringIO(response[1].read_body_bytes())
1169
return LockableFiles.get(self, path)
1172
1190
class RemoteBranchFormat(branch.BranchFormat):
1182
1200
return 'Remote BZR Branch'
1184
1202
def open(self, a_bzrdir):
1185
assert isinstance(a_bzrdir, RemoteBzrDir)
1186
1203
return a_bzrdir.open_branch()
1188
1205
def initialize(self, a_bzrdir):
1189
assert isinstance(a_bzrdir, RemoteBzrDir)
1190
1206
return a_bzrdir.create_branch()
1192
1208
def supports_tags(self):
1252
1276
Used before calls to self._real_branch.
1254
if not self._real_branch:
1255
assert vfs.vfs_enabled()
1278
if self._real_branch is None:
1279
if not vfs.vfs_enabled():
1280
raise AssertionError('smart server vfs must be enabled '
1281
'to use vfs implementation')
1256
1282
self.bzrdir._ensure_real()
1257
1283
self._real_branch = self.bzrdir._real_bzrdir.open_branch()
1258
1284
# Give the remote repository the matching real repo.
1303
1329
repo_token = self.repository.lock_write()
1304
1330
self.repository.unlock()
1305
1331
path = self.bzrdir._path_for_remote_call(self._client)
1306
response = self._client.call('Branch.lock_write', path, branch_token,
1308
if response[0] == 'ok':
1309
ok, branch_token, repo_token = response
1310
return branch_token, repo_token
1311
elif response[0] == 'LockContention':
1312
raise errors.LockContention('(remote lock)')
1313
elif response[0] == 'TokenMismatch':
1314
raise errors.TokenMismatch(token, '(remote token)')
1315
elif response[0] == 'UnlockableTransport':
1316
raise errors.UnlockableTransport(self.bzrdir.root_transport)
1317
elif response[0] == 'ReadOnlyError':
1318
raise errors.ReadOnlyError(self)
1319
elif response[0] == 'LockFailed':
1320
raise errors.LockFailed(response[1], response[2])
1333
response = self._client.call(
1334
'Branch.lock_write', path, branch_token, repo_token or '')
1335
except errors.ErrorFromSmartServer, err:
1336
if err.error_verb == 'LockContention':
1337
raise errors.LockContention('(remote lock)')
1338
elif err.error_verb == 'TokenMismatch':
1339
raise errors.TokenMismatch(token, '(remote token)')
1340
elif err.error_verb == 'UnlockableTransport':
1341
raise errors.UnlockableTransport(self.bzrdir.root_transport)
1342
elif err.error_verb == 'ReadOnlyError':
1343
raise errors.ReadOnlyError(self)
1344
elif err.error_verb == 'LockFailed':
1345
raise errors.LockFailed(err.error_args[0], err.error_args[1])
1347
if response[0] != 'ok':
1322
1348
raise errors.UnexpectedSmartServerResponse(response)
1349
ok, branch_token, repo_token = response
1350
return branch_token, repo_token
1324
1352
def lock_write(self, token=None):
1325
1353
if not self._lock_mode:
1326
1354
remote_tokens = self._remote_lock_write(token)
1327
1355
self._lock_token, self._repo_lock_token = remote_tokens
1328
assert self._lock_token, 'Remote server did not return a token!'
1356
if not self._lock_token:
1357
raise SmartProtocolError('Remote server did not return a token!')
1329
1358
# TODO: We really, really, really don't want to call _ensure_real
1330
1359
# here, but it's the easiest way to ensure coherency between the
1331
1360
# state of the RemoteBranch and RemoteRepository objects and the
1362
1391
def _unlock(self, branch_token, repo_token):
1363
1392
path = self.bzrdir._path_for_remote_call(self._client)
1364
response = self._client.call('Branch.unlock', path, branch_token,
1394
response = self._client.call('Branch.unlock', path, branch_token,
1396
except errors.ErrorFromSmartServer, err:
1397
if err.error_verb == 'TokenMismatch':
1398
raise errors.TokenMismatch(
1399
str((branch_token, repo_token)), '(remote tokens)')
1366
1401
if response == ('ok',):
1368
elif response[0] == 'TokenMismatch':
1369
raise errors.TokenMismatch(
1370
str((branch_token, repo_token)), '(remote tokens)')
1372
raise errors.UnexpectedSmartServerResponse(response)
1403
raise errors.UnexpectedSmartServerResponse(response)
1374
1405
def unlock(self):
1375
1406
self._lock_count -= 1
1416
1448
"""See Branch.last_revision_info()."""
1417
1449
path = self.bzrdir._path_for_remote_call(self._client)
1418
1450
response = self._client.call('Branch.last_revision_info', path)
1419
assert response[0] == 'ok', 'unexpected response code %s' % (response,)
1451
if response[0] != 'ok':
1452
raise SmartProtocolError('unexpected response code %s' % (response,))
1420
1453
revno = int(response[1])
1421
1454
last_revision = response[2]
1422
1455
return (revno, last_revision)
1424
1457
def _gen_revision_history(self):
1425
1458
"""See Branch._gen_revision_history()."""
1426
1459
path = self.bzrdir._path_for_remote_call(self._client)
1427
response = self._client.call_expecting_body(
1460
response_tuple, response_handler = self._client.call_expecting_body(
1428
1461
'Branch.revision_history', path)
1429
assert response[0][0] == 'ok', ('unexpected response code %s'
1431
result = response[1].read_body_bytes().split('\x00')
1462
if response_tuple[0] != 'ok':
1463
raise UnexpectedSmartServerResponse(response_tuple)
1464
result = response_handler.read_body_bytes().split('\x00')
1432
1465
if result == ['']:
1445
1478
rev_id = rev_history[-1]
1446
1479
self._clear_cached_state()
1447
response = self._client.call('Branch.set_last_revision',
1448
path, self._lock_token, self._repo_lock_token, rev_id)
1449
if response[0] == 'NoSuchRevision':
1450
raise NoSuchRevision(self, rev_id)
1452
assert response == ('ok',), (
1453
'unexpected response code %r' % (response,))
1481
response = self._client.call('Branch.set_last_revision',
1482
path, self._lock_token, self._repo_lock_token, rev_id)
1483
except errors.ErrorFromSmartServer, err:
1484
if err.error_verb == 'NoSuchRevision':
1485
raise NoSuchRevision(self, rev_id)
1487
if response != ('ok',):
1488
raise errors.UnexpectedSmartServerResponse(response)
1454
1489
self._cache_revision_history(rev_history)
1456
1491
def get_parent(self):
1498
1530
def is_locked(self):
1499
1531
return self._lock_count >= 1
1501
1534
def set_last_revision_info(self, revno, revision_id):
1503
self._clear_cached_state()
1504
return self._real_branch.set_last_revision_info(revno, revision_id)
1535
revision_id = ensure_null(revision_id)
1536
path = self.bzrdir._path_for_remote_call(self._client)
1538
response = self._client.call('Branch.set_last_revision_info',
1539
path, self._lock_token, self._repo_lock_token, str(revno), revision_id)
1540
except errors.UnknownSmartMethod:
1542
self._clear_cached_state()
1543
return self._real_branch.set_last_revision_info(revno, revision_id)
1544
except errors.ErrorFromSmartServer, err:
1545
if err.error_verb == 'NoSuchRevision':
1546
raise NoSuchRevision(self, err.error_args[0])
1548
if response == ('ok',):
1549
self._clear_cached_state()
1551
raise errors.UnexpectedSmartServerResponse(response)
1506
1553
def generate_revision_history(self, revision_id, last_rev=None,
1507
1554
other_branch=None):
1518
1565
self._ensure_real()
1519
1566
return self._real_branch.set_push_location(location)
1521
def update_revisions(self, other, stop_revision=None, overwrite=False):
1568
def update_revisions(self, other, stop_revision=None, overwrite=False,
1522
1570
self._ensure_real()
1523
1571
return self._real_branch.update_revisions(
1524
other, stop_revision=stop_revision, overwrite=overwrite)
1527
class RemoteBranchConfig(BranchConfig):
1530
self.branch._ensure_real()
1531
return self.branch._real_branch.get_config().username()
1533
def _get_branch_data_config(self):
1534
self.branch._ensure_real()
1535
if self._branch_data_config is None:
1536
self._branch_data_config = TreeConfig(self.branch._real_branch)
1537
return self._branch_data_config
1572
other, stop_revision=stop_revision, overwrite=overwrite,
1540
1576
def _extract_tar(tar, to_dir):