15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
16
16
"""Checkouts and working trees (working copies)."""
19
from bzrlib import urlutils
20
from bzrlib.branch import PullResult
21
from bzrlib.bzrdir import BzrDirFormat, BzrDir
22
from bzrlib.errors import (InvalidRevisionId, NotBranchError, NoSuchFile,
23
NoRepositoryPresent, BzrError, UninitializableFormat,
25
from bzrlib.inventory import Inventory, InventoryFile, InventoryLink
26
from bzrlib.lockable_files import TransportLock, LockableFiles
18
from collections import defaultdict
22
from subvertpy import properties
23
from subvertpy.wc import *
26
import bzrlib, bzrlib.add
31
from bzrlib.branch import (
32
BranchReferenceFormat,
34
from bzrlib.bzrdir import (
39
from bzrlib.errors import (
45
UnsupportedFormatError,
46
UninitializableFormat,
48
from bzrlib.inventory import (
53
from bzrlib.lockable_files import LockableFiles
27
54
from bzrlib.lockdir import LockDir
28
from bzrlib.osutils import file_kind, fingerprint_file, supports_executable
29
55
from bzrlib.revision import NULL_REVISION
30
56
from bzrlib.trace import mutter
31
from bzrlib.revisiontree import RevisionTree
32
from bzrlib.transport.local import LocalTransport
33
from bzrlib.workingtree import WorkingTree, WorkingTreeFormat
57
from bzrlib.transport import get_transport
58
from bzrlib.workingtree import (
35
from bzrlib.plugins.svn.branch import SvnBranch
36
from bzrlib.plugins.svn.commit import _revision_id_to_svk_feature
37
from bzrlib.plugins.svn.convert import SvnConverter
38
from bzrlib.plugins.svn.errors import LocalCommitsUnsupported, NoSvnRepositoryPresent
39
from bzrlib.plugins.svn.mapping import (SVN_PROP_BZR_ANCESTRY, SVN_PROP_BZR_FILEIDS,
40
SVN_PROP_BZR_REVISION_ID, SVN_PROP_BZR_REVISION_INFO,
41
generate_revision_metadata)
63
from bzrlib.plugins.svn import (
64
errors as bzrsvn_errors,
67
from bzrlib.plugins.svn.branch import (
70
from bzrlib.plugins.svn.commit import (
71
_revision_id_to_svk_feature,
73
from bzrlib.plugins.svn.errors import (
76
from bzrlib.plugins.svn.fileids import (
79
from bzrlib.plugins.svn.format import get_rich_root_format
80
from bzrlib.plugins.svn.mapping import escape_svn_path
42
81
from bzrlib.plugins.svn.remote import SvnRemoteAccess
43
82
from bzrlib.plugins.svn.repository import SvnRepository
44
from bzrlib.plugins.svn.svk import SVN_PROP_SVK_MERGE, parse_svk_features, serialize_svk_features
45
from bzrlib.plugins.svn.mapping import escape_svn_path
46
from bzrlib.plugins.svn.transport import (SvnRaTransport, bzr_to_svn_url, create_svn_client,
48
from bzrlib.plugins.svn.tree import SvnBasisTree
53
import svn.core, svn.wc
54
from svn.core import SubversionException
56
from bzrlib.plugins.svn.errors import NoCheckoutSupport
57
from bzrlib.plugins.svn.format import get_rich_root_format
83
from bzrlib.plugins.svn.transport import (
87
from bzrlib.plugins.svn.tree import (
92
def update_wc(adm, basedir, conn, revnum):
93
# FIXME: honor SVN_CONFIG_SECTION_HELPERS:SVN_CONFIG_OPTION_DIFF3_CMD
94
# FIXME: honor SVN_CONFIG_SECTION_MISCELLANY:SVN_CONFIG_OPTION_USE_COMMIT_TIMES
95
# FIXME: honor SVN_CONFIG_SECTION_MISCELLANY:SVN_CONFIG_OPTION_PRESERVED_CF_EXTS
96
editor = adm.get_update_editor(basedir, False, True)
97
assert editor is not None
98
reporter = conn.do_update(revnum, "", True, editor)
99
adm.crawl_revisions(basedir, reporter, restore_files=False,
100
recurse=True, use_commit_times=False)
101
# FIXME: handle externals
104
def apply_prop_changes(orig_props, prop_changes):
105
"""Apply a set of property changes to a properties dictionary.
107
:param orig_props: Dictionary with original properties (will be modified)
108
:param prop_changes: Dictionary of new property values (None for deletion)
109
:return: New dictionary
111
for k,v in prop_changes:
59
119
def generate_ignore_list(ignore_map):
60
120
"""Create a list of ignores, ordered by directory.
66
126
keys = ignore_map.keys()
69
ignores.append("./" + os.path.join(k.strip("/"), ignore_map[k].strip("/")))
127
for k in sorted(keys):
129
if k.strip("/") != "":
130
elements.append(k.strip("/"))
131
elements.append(ignore_map[k].strip("/"))
132
ignores.append("/".join(elements))
73
class SvnWorkingTree(WorkingTree):
74
"""WorkingTree implementation that uses a Subversion Working Copy for storage."""
136
class SvnWorkingTree(WorkingTree, SubversionTree):
137
"""WorkingTree implementation that uses a svn working copy for storage."""
75
139
def __init__(self, bzrdir, local_path, branch):
76
self._format = SvnWorkingTreeFormat()
140
version = check_wc(local_path)
141
self._format = SvnWorkingTreeFormat(version)
77
142
self.basedir = local_path
143
assert isinstance(self.basedir, unicode)
78
144
self.bzrdir = bzrdir
79
145
self._branch = branch
80
146
self.base_revnum = 0
81
self.client_ctx = create_svn_client(bzrdir.svn_url)
82
self.client_ctx.log_msg_func2 = \
83
svn.client.svn_swig_py_get_commit_log_func
86
status = svn.wc.revision_status(self.basedir, None, True, None, None)
87
self.base_revnum = status.max_rev
88
self.base_tree = SvnBasisTree(self)
89
self.base_revid = branch.generate_revision_id(self.base_revnum)
91
self.read_working_inventory()
93
self.controldir = os.path.join(self.basedir, svn.wc.get_adm_dir(),
147
max_rev = revision_status(self.basedir, None, True)[1]
148
self._update_base_revnum(max_rev)
149
self._detect_case_handling()
150
self._transport = bzrdir.get_workingtree_transport(None)
151
self.controldir = os.path.join(bzrdir.svn_controldir, 'bzr')
96
153
os.makedirs(self.controldir)
97
154
os.makedirs(os.path.join(self.controldir, 'lock'))
100
157
control_transport = bzrdir.transport.clone(urlutils.join(
101
svn.wc.get_adm_dir(), 'bzr'))
158
get_adm_dir(), 'bzr'))
102
159
self._control_files = LockableFiles(control_transport, 'lock', LockDir)
160
self.views = self._make_views()
104
162
def get_ignore_list(self):
105
ignores = set([svn.wc.get_adm_dir()])
106
ignores.update(svn.wc.get_default_ignores(svn_config))
163
"""Obtain the list of ignore patterns for this working tree.
165
:note: Will interpret the svn:ignore properties, rather than read
168
ignores = set([get_adm_dir()])
169
ignores.update(svn_config.get_default_ignores())
108
171
def dir_add(wc, prefix, patprefix):
109
ignorestr = svn.wc.prop_get(svn.core.SVN_PROP_IGNORE,
110
self.abspath(prefix).rstrip("/"), wc)
172
ignorestr = wc.prop_get(properties.PROP_IGNORE,
173
self.abspath(prefix).encode("utf-8").rstrip("/"))
111
174
if ignorestr is not None:
112
175
for pat in ignorestr.splitlines():
113
176
ignores.add(urlutils.joinpath(patprefix, pat))
115
entries = svn.wc.entries_read(wc, False)
178
entries = wc.entries_read(False)
116
179
for entry in entries:
120
183
# Ignore ignores on things that aren't directories
121
if entries[entry].kind != svn.core.svn_node_dir:
184
if entries[entry].kind != subvertpy.NODE_DIR:
124
187
subprefix = os.path.join(prefix, entry)
126
subwc = svn.wc.adm_open3(wc, self.abspath(subprefix), False,
189
subwc = self._get_wc(subprefix, base=wc)
129
191
dir_add(subwc, subprefix, urlutils.joinpath(patprefix, entry))
131
svn.wc.adm_close(subwc)
133
195
wc = self._get_wc()
135
197
dir_add(wc, "", ".")
141
203
def is_control_filename(self, path):
142
return svn.wc.is_adm_dir(path)
204
"""Check whether path is a control file (used by bzr or svn)."""
205
return is_adm_dir(path)
144
207
def apply_inventory_delta(self, changes):
145
208
raise NotImplementedError(self.apply_inventory_delta)
147
def update(self, change_reporter=None):
148
rev = svn.core.svn_opt_revision_t()
149
rev.kind = svn.core.svn_opt_revision_head
150
svn.client.update(self.basedir, rev, True, self.client_ctx)
152
def remove(self, files, verbose=False, to_file=None):
210
def _update(self, revnum=None):
212
# FIXME: should be able to use -1 here
213
revnum = self.branch.get_revnum()
214
adm = self._get_wc(write_lock=True, depth=-1)
216
conn = self.branch.repository.transport.get_connection(self.branch.get_branch_path())
218
update_wc(adm, self.basedir, conn, revnum)
220
self.branch.repository.transport.add_connection(conn)
225
def update(self, change_reporter=None, possible_transports=None,
227
"""Update the workingtree to a new Bazaar revision number.
230
orig_revnum = self.base_revnum
231
self._update_base_revnum(self._update(revnum))
232
return self.base_revnum - orig_revnum
234
def remove(self, files, verbose=False, to_file=None, keep_files=True,
236
"""Remove files from the working tree."""
153
237
# FIXME: Use to_file argument
154
238
# FIXME: Use verbose argument
155
239
assert isinstance(files, list)
156
240
wc = self._get_wc(write_lock=True)
158
242
for file in files:
159
svn.wc.delete2(self.abspath(file), wc, None, None, None)
243
wc.delete(self.abspath(file).encode("utf-8"))
163
247
for file in files:
164
248
self._change_fileid_mapping(None, file)
165
249
self.read_working_inventory()
167
def _get_wc(self, relpath="", write_lock=False):
168
return svn.wc.adm_open3(None, self.abspath(relpath).rstrip("/"),
252
def _get_wc(self, relpath="", write_lock=False, depth=0, base=None):
253
"""Open a working copy handle."""
254
return WorkingCopy(base,
255
self.abspath(relpath).rstrip("/").encode("utf-8"),
171
258
def _get_rel_wc(self, relpath, write_lock=False):
172
259
dir = os.path.dirname(relpath)
263
362
def find_copies(url, relpath=""):
363
"""Find copies of the specified path
365
:param url: URL of which to find copies
366
:param relpath: Optional subpath to search in
367
:return: Yields all copies
264
370
wc = self._get_wc(relpath)
265
entries = svn.wc.entries_read(wc, False)
266
for entry in entries.values():
267
subrelpath = os.path.join(relpath, entry.name)
268
if entry.name == "" or entry.kind != 'directory':
269
if ((entry.copyfrom_url == url or entry.url == url) and
270
not (entry.schedule in (svn.wc.schedule_delete,
271
svn.wc.schedule_replace))):
273
self.branch.get_branch_path().strip("/"),
276
find_copies(subrelpath)
372
entries = wc.entries_read(False)
373
for entry in entries.values():
374
subrelpath = os.path.join(relpath, entry.name)
375
if entry.name == "" or entry.kind != 'directory':
376
if ((entry.copyfrom_url == url or entry.url == url) and
377
not (entry.schedule in (SCHEDULE_DELETE,
379
ret.append(os.path.join(
380
self.branch.get_branch_path().strip("/"),
383
find_copies(subrelpath)
279
388
def find_ids(entry, rootwc):
280
389
relpath = urllib.unquote(entry.url[len(entry.repos):].strip("/"))
281
assert entry.schedule in (svn.wc.schedule_normal,
282
svn.wc.schedule_delete,
284
svn.wc.schedule_replace)
285
if entry.schedule == svn.wc.schedule_normal:
390
assert entry.schedule in (SCHEDULE_NORMAL,
394
if entry.schedule == SCHEDULE_NORMAL:
286
395
assert entry.revision >= 0
288
397
return self.path_to_file_id(entry.cmt_rev, entry.revision,
290
elif entry.schedule == svn.wc.schedule_delete:
399
elif entry.schedule == SCHEDULE_DELETE:
291
400
return (None, None)
292
elif (entry.schedule == svn.wc.schedule_add or
293
entry.schedule == svn.wc.schedule_replace):
401
elif (entry.schedule == SCHEDULE_ADD or
402
entry.schedule == SCHEDULE_REPLACE):
294
403
# See if the file this file was copied from disappeared
295
404
# and has no other copies -> in that case, take id of other file
296
405
if (entry.copyfrom_url and
330
439
entry = entries[name]
333
if entry.kind == svn.core.svn_node_dir:
334
subwc = svn.wc.adm_open3(wc, self.abspath(subrelpath),
442
if entry.kind == subvertpy.NODE_DIR:
443
subwc = self._get_wc(subrelpath, base=wc)
445
assert isinstance(subrelpath, unicode)
337
446
add_dir_to_inv(subrelpath, subwc, id)
339
svn.wc.adm_close(subwc)
341
450
(subid, subrevid) = find_ids(entry, rootwc)
452
assert isinstance(subrelpath, unicode)
343
453
add_file_to_inv(subrelpath, subid, subrevid, id)
345
mutter('no id for %r' % entry.url)
455
mutter('no id for %r', entry.url)
347
457
rootwc = self._get_wc()
349
459
add_dir_to_inv(u"", rootwc, None)
351
svn.wc.adm_close(rootwc)
353
463
self._set_inventory(inv, dirty=False)
356
466
def set_last_revision(self, revid):
357
mutter('setting last revision to %r' % revid)
467
mutter('setting last revision to %r', revid)
358
468
if revid is None or revid == NULL_REVISION:
359
469
self.base_revid = revid
360
470
self.base_revnum = 0
361
self.base_tree = RevisionTree(self, Inventory(), revid)
471
self.base_tree = None
364
474
rev = self.branch.lookup_revision_id(revid)
365
475
self.base_revnum = rev
366
476
self.base_revid = revid
367
self.base_tree = SvnBasisTree(self)
369
# TODO: Implement more efficient version
370
newrev = self.branch.repository.get_revision(revid)
371
newrevtree = self.branch.repository.revision_tree(revid)
373
def update_settings(wc, path):
374
id = newrevtree.inventory.path2id(path)
375
mutter("Updating settings for %r" % id)
376
revnum = self.branch.lookup_revision_id(
377
newrevtree.inventory[id].revision)
379
svn.wc.process_committed2(self.abspath(path).rstrip("/"), wc,
381
svn.core.svn_time_to_cstring(newrev.timestamp),
382
newrev.committer, None, False)
384
if newrevtree.inventory[id].kind != 'directory':
387
entries = svn.wc.entries_read(wc, True)
388
for entry in entries:
392
subwc = svn.wc.adm_open3(wc, os.path.join(self.basedir, path, entry), False, 0, None)
477
self.base_tree = None
479
def set_parent_trees(self, parents_list, allow_leftmost_as_ghost=False):
480
"""See MutableTree.set_parent_trees."""
481
self.set_parent_ids([rev for (rev, tree) in parents_list])
483
def set_parent_ids(self, parent_ids):
484
"""See MutableTree.set_parent_ids."""
485
super(SvnWorkingTree, self).set_parent_ids(parent_ids)
486
if parent_ids == [] or parent_ids[0] == NULL_REVISION:
489
merges = parent_ids[1:]
490
adm = self._get_wc(write_lock=True)
492
svk_merges = svk.parse_svk_features(self._get_svk_merges(self._get_base_branch_props()))
394
update_settings(subwc, os.path.join(path, entry))
396
svn.wc.adm_close(subwc)
398
# Set proper version for all files in the wc
399
wc = self._get_wc(write_lock=True)
401
update_settings(wc, "")
404
self.base_revid = revid
406
def commit(self, message=None, message_callback=None, revprops=None,
407
timestamp=None, timezone=None, committer=None, rev_id=None,
408
allow_pointless=True, strict=False, verbose=False, local=False,
409
reporter=None, config=None, specific_files=None, author=None):
410
if author is not None:
411
revprops['author'] = author
412
# FIXME: Use allow_pointless
414
# FIXME: Use reporter
417
raise LocalCommitsUnsupported()
420
specific_files = [self.abspath(x).encode('utf8') for x in specific_files]
422
specific_files = [self.basedir.encode('utf8')]
424
if message_callback is not None:
425
def log_message_func(items, pool):
426
""" Simple log message provider for unit tests. """
427
return message_callback(self).encode("utf-8")
429
assert isinstance(message, basestring)
430
def log_message_func(items, pool):
431
""" Simple log message provider for unit tests. """
432
return message.encode("utf-8")
434
self.client_ctx.log_msg_baton2 = log_message_func
435
if rev_id is not None:
436
extra = "%d %s\n" % (self.branch.revno()+1, rev_id)
439
wc = self._get_wc(write_lock=True)
441
svn.wc.prop_set(SVN_PROP_BZR_REVISION_ID+str(self.branch.mapping.scheme),
442
self._get_bzr_revids(self._get_base_branch_props()) + extra,
444
svn.wc.prop_set(SVN_PROP_BZR_REVISION_INFO,
445
generate_revision_metadata(timestamp,
455
commit_info = svn.client.commit3(specific_files, True, False,
457
except SubversionException, (_, num):
458
if num == svn.core.SVN_ERR_FS_TXN_OUT_OF_DATE:
459
raise OutOfDateTree(self)
462
# Reset properties so the next subversion commit won't
463
# accidently set these properties.
464
wc = self._get_wc(write_lock=True)
465
base_branch_props = self._get_base_branch_props()
466
svn.wc.prop_set(SVN_PROP_BZR_REVISION_ID+str(self.branch.mapping.scheme),
467
self._get_bzr_revids(base_branch_props), self.basedir, wc)
468
svn.wc.prop_set(SVN_PROP_BZR_REVISION_INFO,
469
base_branch_props.get(SVN_PROP_BZR_REVISION_INFO, ""),
474
self.client_ctx.log_msg_baton2 = None
476
revid = self.branch.generate_revision_id(commit_info.revision)
478
self.base_revid = revid
479
self.base_revnum = commit_info.revision
480
self.base_tree = SvnBasisTree(self)
497
svk_merges.add(_revision_id_to_svk_feature(merge,
498
self.branch.repository.lookup_revision_id))
499
except NoSuchRevision:
502
adm.prop_set(svk.SVN_PROP_SVK_MERGE,
503
svk.serialize_svk_features(svk_merges), self.basedir)
484
507
def smart_add(self, file_list, recurse=True, action=None, save=True):
508
"""See MutableTree.smart_add()."""
485
509
assert isinstance(recurse, bool)
486
510
if action is None:
487
511
action = bzrlib.add.AddAction()
534
559
wc = self._get_wc(os.path.dirname(f), write_lock=True)
537
svn.wc.add2(os.path.join(self.basedir, f), wc, None, 0,
562
wc.add(self.abspath(f).encode("utf-8"))
539
563
if ids is not None:
540
564
self._change_fileid_mapping(ids.next(), f, wc)
541
except SubversionException, (_, num):
542
if num == svn.core.SVN_ERR_ENTRY_EXISTS:
565
except subvertpy.SubversionException, (_, num):
566
if num == subvertpy.ERR_ENTRY_EXISTS:
544
elif num == svn.core.SVN_ERR_WC_PATH_NOT_FOUND:
568
elif num == subvertpy.ERR_WC_PATH_NOT_FOUND:
545
569
raise NoSuchFile(path=f)
549
573
self.read_working_inventory()
551
575
def basis_tree(self):
576
"""Return the basis tree for a working tree."""
552
577
if self.base_revid is None or self.base_revid == NULL_REVISION:
553
578
return self.branch.repository.revision_tree(self.base_revid)
580
if self.base_tree is None:
581
self.base_tree = SvnBasisTree(self)
555
583
return self.base_tree
585
def _update_base_revnum(self, fetched):
586
self.base_revnum = fetched
587
self.base_revid = self.branch.generate_revision_id(fetched)
588
self.base_tree = None
589
self.read_working_inventory()
557
591
def pull(self, source, overwrite=False, stop_revision=None,
558
592
delta_reporter=None, possible_transports=None):
593
"""Pull in changes from another branch into this working tree."""
559
594
# FIXME: Use delta_reporter
560
596
# FIXME: Use overwrite
561
result = PullResult()
562
result.source_branch = source
563
result.master_branch = None
564
result.target_branch = self.branch
565
(result.old_revno, result.old_revid) = self.branch.last_revision_info()
566
if stop_revision is None:
567
stop_revision = self.branch.last_revision()
568
rev = svn.core.svn_opt_revision_t()
569
rev.kind = svn.core.svn_opt_revision_number
570
rev.value.number = self.branch.lookup_revision_id(stop_revision)
571
fetched = svn.client.update(self.basedir, rev, True, self.client_ctx)
572
self.base_revid = self.branch.generate_revision_id(fetched)
573
result.new_revid = self.base_revid
574
result.new_revno = self.branch.revision_id_to_revno(result.new_revid)
597
result = self.branch.pull(source, overwrite=overwrite, stop_revision=stop_revision)
598
fetched = self._update(self.branch.get_revnum())
599
self._update_base_revnum(fetched)
602
def get_file_properties(self, file_id, path=None):
604
path = self._inventory.id2path(file_id)
607
(prop_changes, orig_props) = wc.get_prop_diffs(self.basedir)
610
return apply_prop_changes(orig_props, prop_changes)
577
612
def get_file_sha1(self, file_id, path=None, stat_value=None):
613
"""Determine the SHA1 for a file."""
579
615
path = self._inventory.id2path(file_id)
580
return fingerprint_file(open(self.abspath(path)))['sha1']
616
return osutils.fingerprint_file(open(self.abspath(path)))['sha1']
582
618
def _change_fileid_mapping(self, id, path, wc=None):
584
620
subwc = self._get_wc(write_lock=True)
587
new_entries = self._get_new_file_ids(subwc)
589
if new_entries.has_key(path):
590
del new_entries[path]
592
assert isinstance(id, str)
593
new_entries[path] = id
594
existing = "".join(map(lambda (path, id): "%s\t%s\n" % (path, id), new_entries.items()))
596
svn.wc.prop_set(SVN_PROP_BZR_FILEIDS, existing.encode("utf-8"), self.basedir, subwc)
598
svn.wc.adm_close(subwc)
624
new_entries = self._get_new_file_ids(subwc)
626
if new_entries.has_key(path):
627
del new_entries[path]
629
assert isinstance(id, str)
630
new_entries[path] = id
631
fileprops = self._get_branch_props()
632
self.branch.mapping.export_fileid_map_fileprops(new_entries, fileprops)
633
self._set_branch_props(subwc, fileprops)
638
def _get_changed_branch_props(self):
642
(prop_changes, orig_props) = wc.get_prop_diffs(self.basedir)
643
for k,v in prop_changes:
644
ret[k] = (orig_props.get(k), v)
649
def _get_branch_props(self):
652
(prop_changes, orig_props) = wc.get_prop_diffs(self.basedir)
653
return apply_prop_changes(orig_props, prop_changes)
657
def _set_branch_props(self, wc, fileprops):
658
for k,v in fileprops.iteritems():
659
wc.prop_set(k, v, self.basedir)
600
661
def _get_base_branch_props(self):
601
return self.branch.repository.branchprop_list.get_properties(
602
self.branch.get_branch_path(self.base_revnum), self.base_revnum)
664
(prop_changes, orig_props) = wc.get_prop_diffs(self.basedir)
604
669
def _get_new_file_ids(self, wc):
605
committed = self._get_base_branch_props().get(SVN_PROP_BZR_FILEIDS, "")
606
existing = svn.wc.prop_get(SVN_PROP_BZR_FILEIDS, self.basedir, wc)
607
if existing is None or committed == existing:
609
return dict(map(lambda x: str(x).split("\t"),
610
existing.splitlines()))
612
def _get_bzr_revids(self, base_branch_props):
613
return base_branch_props.get(SVN_PROP_BZR_REVISION_ID+str(self.branch.mapping.scheme), "")
615
def _get_bzr_merges(self, base_branch_props):
616
return base_branch_props.get(SVN_PROP_BZR_ANCESTRY+str(self.branch.mapping.scheme), "")
670
return self.branch.mapping.import_fileid_map_fileprops(
671
self._get_changed_branch_props())
618
673
def _get_svk_merges(self, base_branch_props):
619
return base_branch_props.get(SVN_PROP_SVK_MERGE, "")
621
def set_pending_merges(self, merges):
622
"""See MutableTree.set_pending_merges()."""
623
wc = self._get_wc(write_lock=True)
627
bzr_merge = "\t".join(merges) + "\n"
631
svn.wc.prop_set(SVN_PROP_BZR_ANCESTRY+str(self.branch.mapping.scheme),
632
self._get_bzr_merges(self._get_base_branch_props()) + bzr_merge,
635
svk_merges = parse_svk_features(self._get_svk_merges(self._get_base_branch_props()))
640
svk_merges.add(_revision_id_to_svk_feature(merge))
641
except InvalidRevisionId:
644
svn.wc.prop_set2(SVN_PROP_SVK_MERGE,
645
serialize_svk_features(svk_merges), self.basedir,
650
def add_pending_merge(self, revid):
651
merges = self.pending_merges()
653
self.set_pending_merges(merges)
655
def pending_merges(self):
656
merged = self._get_bzr_merges(self._get_base_branch_props()).splitlines()
659
merged_data = svn.wc.prop_get(
660
SVN_PROP_BZR_ANCESTRY+str(self.branch.mapping.scheme), self.basedir, wc)
661
if merged_data is None:
664
set_merged = merged_data.splitlines()
668
assert (len(merged) == len(set_merged) or
669
len(merged)+1 == len(set_merged))
671
if len(set_merged) > len(merged):
672
return set_merged[-1].split("\t")
674
return base_branch_props.get(svk.SVN_PROP_SVK_MERGE, "")
676
def apply_inventory_delta(self, delta):
679
def _last_revision(self):
680
return self.base_revid
682
def path_content_summary(self, path, _lstat=os.lstat,
683
_mapper=osutils.file_kind_from_stat_mode):
684
"""See Tree.path_content_summary."""
685
abspath = self.abspath(path)
687
stat_result = _lstat(abspath)
689
if getattr(e, 'errno', None) == errno.ENOENT:
691
return ('missing', None, None, None)
692
# propagate other errors
694
kind = _mapper(stat_result.st_mode)
696
size = stat_result.st_size
697
# try for a stat cache lookup
698
executable = self._is_executable_from_path_and_stat(path, stat_result)
699
return (kind, size, executable, self._sha_from_stat(
701
elif kind == 'directory':
702
return kind, None, None, None
703
elif kind == 'symlink':
704
return ('symlink', None, None, os.readlink(abspath.encode(osutils._fs_enc)))
706
return (kind, None, None, None)
708
def _get_base_revmeta(self):
709
return self.branch.repository._revmeta_provider.lookup_revision(self.branch.get_branch_path(self.base_revnum), self.base_revnum)
676
711
def _reset_data(self):
714
def break_lock(self):
715
"""Break a lock if one is present from another instance.
717
Uses the ui factory to ask for confirmation if the lock may be from
720
This will probe the repository for its lock as well.
722
if getattr(subvertpy.wc, "cleanup", None) is not None:
723
subvertpy.wc.cleanup(self.basedir)
724
self._control_files.break_lock()
679
726
def unlock(self):
680
727
# non-implementation specific cleanup
694
741
# Default to not executable
744
def update_basis_by_delta(self, new_revid, delta):
745
"""Update the parents of this tree after a commit.
747
This gives the tree one parent, with revision id new_revid. The
748
inventory delta is applied to the current basis tree to generate the
749
inventory for the parent new_revid, and all other parent trees are
752
All the changes in the delta should be changes synchronising the basis
753
tree with some or all of the working tree, with a change to a directory
754
requiring that its contents have been recursively included. That is,
755
this is not a general purpose tree modification routine, but a helper
756
for commit which is not required to handle situations that do not arise
759
:param new_revid: The new revision id for the trees parent.
760
:param delta: An inventory delta (see apply_inventory_delta) describing
761
the changes from the current left most parent revision to new_revid.
763
rev = self.branch.lookup_revision_id(new_revid)
764
self.base_revnum = rev
765
self.base_revid = new_revid
766
self.base_tree = None
768
# TODO: Implement more efficient version
769
newrev = self.branch.repository.get_revision(new_revid)
770
newrevtree = self.branch.repository.revision_tree(new_revid)
771
svn_revprops = self.branch.repository._log.revprop_list(rev)
773
def process_committed(adm, relpath, revid, svn_revprops):
774
mutter("process %r -> %r", relpath, revid)
775
abspath = self.abspath(relpath).encode("utf-8").rstrip("/")
776
adm.process_committed(abspath,
777
False, self.branch.lookup_revision_id(revid),
778
svn_revprops[properties.PROP_REVISION_DATE],
779
svn_revprops.get(properties.PROP_REVISION_AUTHOR, ""))
780
mutter("doneprocess %r -> %r", relpath, revid)
782
def update_settings(adm, path, id):
783
if newrevtree.inventory[id].kind != 'directory':
786
entries = adm.entries_read(False)
787
for name, entry in entries.iteritems():
789
process_committed(adm, path,
790
newrevtree.inventory[id].revision,
794
child_path = os.path.join(path, name.decode("utf-8"))
796
child_id = newrevtree.inventory.path2id(child_path)
798
child_abspath = self.abspath(child_path).encode("utf-8").rstrip("/")
800
if not child_id in newrevtree.inventory:
802
elif newrevtree.inventory[child_id].kind == 'directory':
803
subwc = self._get_wc(child_path, write_lock=True, base=adm)
805
update_settings(subwc, child_path, child_id)
809
pristine_abspath = get_pristine_copy_path(child_abspath)
810
if os.path.exists(pristine_abspath):
811
os.remove(pristine_abspath)
812
source_f = open(child_abspath, "r")
813
target_f = open(pristine_abspath, "w")
814
target_f.write(source_f.read())
817
process_committed(adm, child_path,
818
newrevtree.inventory[child_id].revision,
821
# Set proper version for all files in the wc
822
adm = self._get_wc(write_lock=True)
824
update_settings(adm, "", newrevtree.inventory.root.file_id)
828
self.set_parent_ids([new_revid])
698
831
class SvnWorkingTreeFormat(WorkingTreeFormat):
699
832
"""Subversion working copy format."""
833
def __init__(self, version):
834
self.version = version
700
836
def __get_matchingbzrdir(self):
701
837
return SvnWorkingTreeDirFormat()
703
839
_matchingbzrdir = property(__get_matchingbzrdir)
705
841
def get_format_description(self):
706
return "Subversion Working Copy"
842
return "Subversion Working Copy Version %d" % self.version
708
844
def get_format_string(self):
709
return "Subversion Working Copy Format"
845
raise NotImplementedError
711
847
def initialize(self, a_bzrdir, revision_id=None):
712
848
raise NotImplementedError(self.initialize)
723
859
self.local_path = transport.local_abspath(".")
725
861
# Open related remote repository + branch
726
wc = svn.wc.adm_open3(None, self.local_path, False, 0, None)
728
self.svn_url = svn.wc.entry(self.local_path, wc, True).url
863
wc = WorkingCopy(None, self.local_path)
864
except subvertpy.SubversionException, (msg, ERR_WC_UNSUPPORTED_FORMAT):
865
raise UnsupportedFormatError(msg, kind='workingtree')
867
self.svn_url = wc.entry(self.local_path, True).url
732
self.remote_transport = SvnRaTransport(self.svn_url)
733
self.remote_bzrdir = SvnRemoteAccess(self.remote_transport)
734
self.svn_root_transport = self.remote_transport.clone_root()
871
self._remote_transport = None
872
self._remote_bzrdir = None
873
self.svn_controldir = os.path.join(self.local_path, get_adm_dir())
735
874
self.root_transport = self.transport = transport
876
def backup_bzrdir(self):
877
self.root_transport.copy_tree(".svn", ".svn.backup")
878
return (self.root_transport.abspath(".svn"),
879
self.root_transport.abspath(".svn.backup"))
881
def is_control_filename(self, filename):
882
return filename == '.svn' or filename.startswith('.svn/')
884
def get_remote_bzrdir(self):
885
if self._remote_bzrdir is None:
886
self._remote_bzrdir = SvnRemoteAccess(self.get_remote_transport())
887
return self._remote_bzrdir
889
def get_remote_transport(self):
890
if self._remote_transport is None:
891
self._remote_transport = SvnRaTransport(self.svn_url)
892
return self._remote_transport
737
894
def clone(self, path, revision_id=None, force_new_repo=False):
738
895
raise NotImplementedError(self.clone)
740
897
def open_workingtree(self, _unsupported=False, recommend_upgrade=False):
741
return SvnWorkingTree(self, self.local_path, self.open_branch())
899
return SvnWorkingTree(self, self.local_path, self.open_branch())
900
except bzrsvn_errors.NotSvnBranchPath, e:
901
raise NoWorkingTree(self.local_path)
743
903
def sprout(self, url, revision_id=None, force_new_repo=False,
744
904
recurse='down', possible_transports=None, accelerator_tree=None,